העברת בקשות יוצאות

כברירת מחדל, סביבת זמן הריצה של Python 2.7 משתמשת בשירות אחזור של כתובות אתרים כדי לטפל בבקשות HTTP(S) יוצאות, גם אם משתמשים בספריות Python‏ urllib,‏ urllib2 או httplib כדי להנפיק את הבקשות האלה. השירות URL Fetch לא מטפל בבקשות מהספרייה requests, אלא אם מפעילים אותו באופן מפורש.

זמן הריצה של Python 3 לא צריך שירות ביניים כדי לטפל בבקשות יוצאות. אם אתם רוצים להפסיק להשתמש ב-URL Fetch APIs אבל עדיין צריכים פונקציונליות דומה, כדאי להעביר את הבקשות האלה לשימוש בספריית Python רגילה, כמו ספריית requests.

ההבדלים העיקריים בין URL Fetch לבין ספריות Python רגילות

  • מגבלת הגודל והמכסות של בקשות שמטופלות על ידי URL Fetch שונות ממגבלת הגודל והמכסות של בקשות שלא מטופלות על ידי URL Fetch.

  • כשמשתמשים ב-URL Fetch, אם האפליקציה שולחת בקשה לאפליקציית App Engine אחרת,‏ URL Fetch מוסיף את כותרת הבקשה X-Appengine-Inbound-Appid כדי לאשר את הזהות של האפליקציה. האפליקציה שמקבלת את הבקשה יכולה להשתמש בזהות כדי לקבוע אם לעבד את הבקשה.

    הכותרת הזו זמינה רק בבקשות שנשלחות מהאפליקציה שלכם אם היא משתמשת ב-URL Fetch. אם אתם או צד שלישי מוסיפים את הכותרת לבקשה, App Engine מסיר אותה.

    למידע על הצהרה ואימות של זהות בלי להשתמש ב-URL Fetch, אפשר לעיין במאמר העברת זהות האפליקציה לאסימונים מזהים מסוג OIDC.

    דוגמה לשימוש בכותרת הבקשה כדי לאמת את הזהות של האפליקציה שקוראת ל-API כשבקשות נשלחות בין אפליקציות App Engine מופיעה בדוגמה לבקשה מ-App Engine אל App Engine.

  • אפשר להשתמש ב-URL Fetch כדי להגדיר זמן קצוב לתפוגה כברירת מחדל לכל הבקשות. ברוב הספריות של Python 3, כמו requests ו-urllib, הגדרת ברירת המחדל של הזמן הקצוב לתפוגה היא None, ולכן צריך לעדכן כל בקשה שהקוד מבצע כדי לציין זמן קצוב לתפוגה.

סקירה כללית של תהליך המיגרציה

  1. אם האפליקציה שלכם משתמשת ב-URL Fetch APIs כדי לשלוח בקשות, צריך לעדכן את הקוד כדי להשתמש במקום זאת בספריית Python רגילה. מומלץ לציין זמן קצוב לתפוגה לכל בקשה.

  2. בודקים את הבקשות היוצאות בשרת הפיתוח המקומי.

  3. מגדירים את האפליקציה כך שתדלג על URL Fetch כשהיא פועלת ב-App Engine.

  4. פורסים את האפליקציה.

החלפת ממשקי URL Fetch API בספריית Python

  1. אם אתם לא משתמשים כבר בספריית Python רגילה כדי לשלוח בקשות יוצאות, אתם צריכים לבחור ספרייה ולהוסיף אותה לתלות של האפליקציה.

    לדוגמה, כדי להשתמש בספריית הבקשות, יוצרים קובץ requirements.txt באותה תיקייה שבה נמצא הקובץ app.yaml ומוסיפים את השורה הבאה:

    requests==2.24.0
    

    כדי להבטיח תאימות ל-Python 2, מומלץ להצמיד את ספריית requests לגרסה 2.24.0. כשפורסים את האפליקציה, App Engine מוריד את כל יחסי התלות שמוגדרים בקובץ requirements.txt.

    לפיתוח מקומי, מומלץ להתקין תלות בסביבה וירטואלית כמו venv.

  2. מחפשים בקוד שימוש במודול google.appengine.api.urlfetch ומעדכנים את הקוד כך שישתמש בספריית Python.

יצירת בקשות HTTPS פשוטות

בדוגמה הבאה מוצג איך לשלוח בקשת HTTPS רגילה באמצעות הספרייה requests:

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

from flask import Flask

import requests


app = Flask(__name__)


@app.route("/")
def index():
    url = "http://www.google.com/humans.txt"
    response = requests.get(url)
    response.raise_for_status()
    return response.text


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )


if __name__ == "__main__":
    # This is used when running locally.
    app.run(host="127.0.0.1", port=8080, debug=True)

שליחת בקשות HTTPS אסינכרוניות

בדוגמה הבאה מוצג איך לשלוח בקשת HTTPS אסינכרונית באמצעות הספרייה requests:

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from time import sleep

from flask import Flask
from flask import make_response
from requests_futures.sessions import FuturesSession


TIMEOUT = 10  # Wait this many seconds for background calls to finish
app = Flask(__name__)


@app.route("/")  # Fetch and return remote page asynchronously
def get_async():
    session = FuturesSession()
    url = "http://www.google.com/humans.txt"

    rpc = session.get(url)

    # ... do other things ...

    resp = make_response(rpc.result().text)
    resp.headers["Content-type"] = "text/plain"
    return resp


@app.route("/callback")  # Fetch and return remote pages using callback
def get_callback():
    global response_text
    global counter

    response_text = ""
    counter = 0

    def cb(resp, *args, **kwargs):
        global response_text
        global counter

        if 300 <= resp.status_code < 400:
            return  # ignore intermediate redirection responses

        counter += 1
        response_text += "Response number {} is {} bytes from {}\n".format(
            counter, len(resp.text), resp.url
        )

    session = FuturesSession()
    urls = [
        "https://google.com/",
        "https://www.google.com/humans.txt",
        "https://www.github.com",
        "https://www.travis-ci.org",
    ]

    futures = [session.get(url, hooks={"response": cb}) for url in urls]

    # No wait functionality in requests_futures, so check every second to
    # see if all callbacks are done, up to TIMEOUT seconds
    for elapsed_time in range(TIMEOUT + 1):
        all_done = True
        for future in futures:
            if not future.done():
                all_done = False
                break
        if all_done:
            break
        sleep(1)

    resp = make_response(response_text)
    resp.headers["Content-type"] = "text/plain"
    return resp


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )

בדיקה מקומית

אם עדכנתם בקשות יוצאות, מריצים את האפליקציה בשרת הפיתוח המקומי ומוודאים שהבקשות מצליחות.

דילוג על אחזור כתובת URL

כדי להפסיק את הטיפול בבקשות של URL Fetch כשפורסים את האפליקציה ב-App Engine:

  1. בקובץ app.yaml, מגדירים את משתנה הסביבה GAE_USE_SOCKETS_HTTPLIB לכל ערך. הערך יכול להיות כל ערך, כולל מחרוזת ריקה. לדוגמה:

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. אם הפעלתם את URL Fetch כדי לטפל בבקשות שנשלחות מספריית requests, אתם יכולים להסיר את הבקשות AppEngineAdapter מהאפליקציה.

    לדוגמה, להסיר את requests_toolbelt.adapters.appengine מהקובץ appengine_config.py ואת requests_toolbelt.adapters.appengine.monkeypatch() מקובצי Python.

שימו לב: גם אם תעקפו את URL Fetch כמו שמתואר בשלבים הקודמים, האפליקציה שלכם עדיין יכולה להשתמש ישירות ב-URL Fetch API.

פריסת האפליקציה

כשמוכנים לפרוס את האפליקציה, צריך:

  1. בדיקת האפליקציה ב-App Engine.

    כדי לוודא שהאפליקציה לא מבצעת קריאות ל-API של Url Fetch, מעיינים בדף Quotas (מכסות) של App Engine במסוף Google Cloud .

    צפייה בקריאות ל-API של אחזור כתובות URL

  2. אם האפליקציה פועלת ללא שגיאות, משתמשים בפיצול תנועה כדי להגדיל בהדרגה את התנועה לאפליקציה המעודכנת. חשוב לעקוב מקרוב אחרי האפליקציה כדי לזהות בעיות לפני שמפנים אליה עוד תנועה.