Migrer des requêtes sortantes

Par défaut, l'environnement d'exécution Python 2.7 utilise le service de récupération d'URL (URL Fetch) pour gérer les requêtes HTTP(S) sortantes, même si vous utilisez des bibliothèques Python urllib, urllib2 ou httplib pour les émettre. URL Fetch ne gère pas les requêtes de la bibliothèque requests, sauf si vous activez explicitement cette fonctionnalité.

L'environnement d'exécution Python 3 ne nécessite pas de service intermédiaire pour gérer les requêtes sortantes. Si vous ne souhaitez plus utiliser les API URL Fetch pour effectuer la migration mais que vous avez toujours besoin de fonctionnalités similaires, vous devez migrer ces requêtes pour utiliser une bibliothèque Python standard telle que la bibliothèque requests.

Principales différences entre URL Fetch et les bibliothèques Python standards

  • La taille limite et les quotas applicables aux requêtes gérées par URL Fetch sont différents de la taille limite et des quotas applicables aux requêtes qui ne sont pas gérées par URL Fetch.

  • Avec URL Fetch, lorsque votre application envoie une requête à une autre application App Engine, URL Fetch ajoute l'en-tête de requête X-Appengine-Inbound-Appid pour revendiquer l'identité de l'application. L'application qui reçoit la requête peut ainsi déterminer si elle doit traiter la requête ou non en se basant sur l'identité.

    Cet en-tête n'est disponible que dans les requêtes envoyées à partir de votre application si elle utilise URL Fetch. App Engine supprime l'en-tête si vous ou une autre personne l'ajoutez à une requête.

    Pour en savoir plus sur la revendication et la validation d'identité sans utiliser URL Fetch, consultez Faire migrer App Identity vers des jetons d'identification OIDC.

    Pour obtenir un exemple d'utilisation de l'en-tête de requête pour vérifier l'identité de l'application appelante lorsque des requêtes sont envoyées entre les applications App Engine, consultez la page Exemple de requête d'App Engine vers App Engine.

  • Vous pouvez vous servir d'URL Fetch pour définir un délai d'expiration par défaut pour toutes les requêtes. La plupart des bibliothèques Python 3, telles que requests et urllib, définissent le délai avant expiration par défaut sur None. Vous devez donc mettre à jour chaque requête que votre code effectue pour spécifier un délai avant expiration.

Présentation du processus de migration

  1. Si votre application utilise des API URL Fetch pour effectuer des requêtes, mettez à jour votre code pour qu'il utilise plutôt une bibliothèque Python standard. Nous vous recommandons de spécifier un délai avant expiration pour chaque requête.

  2. Testez vos requêtes sortantes sur le serveur de développement local.

  3. Configurez votre application pour qu'elle contourne URL Fetch lorsque vous l'exécutez dans App Engine.

  4. Déployez l'application.

Remplacer les API URL Fetch par une bibliothèque Python

  1. Si vous n'utilisez pas encore de bibliothèque Python standard pour émettre des requêtes sortantes, choisissez une bibliothèque et ajoutez-la aux dépendances de votre application.

    Par exemple, pour utiliser la bibliothèque Requests, créez un fichier requirements.txt dans le même dossier que celui où se trouve votre fichier app.yaml, puis ajoutez la ligne suivante :

    requests==2.24.0
    

    Pour assurer la compatibilité avec Python 2, nous vous recommandons d'épingler la bibliothèque requests à la version 2.24.0. Lorsque vous déployez votre application, App Engine télécharge toutes les dépendances définies dans le fichier requirements.txt.

    Pour le développement local, nous vous recommandons d'installer des dépendances dans un environnement virtuel tel que venv.

  2. Recherchez dans votre code toute utilisation du module google.appengine.api.urlfetch, puis mettez le code à jour pour qu'il utilise votre bibliothèque Python.

Effectuer des requêtes HTTPS simples

L'exemple suivant montre comment effectuer une requête HTTPS standard à l'aide de la bibliothèque 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)

Effectuer des requêtes HTTPS asynchrones

L'exemple suivant montre comment effectuer une requête HTTPS asynchrone à l'aide de la bibliothèque 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, make_response

from requests_futures.sessions import FuturesSession
from time import sleep

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

Tester localement

Si vous avez mis à jour l'une de vos requêtes sortantes, exécutez votre application sur le serveur de développement local et confirmez que les requêtes aboutissent.

Contourner le processus de récupération d'URL

Pour empêcher URL Fetch de gérer les requêtes lorsque vous déployez votre application sur App Engine, procédez comme suit :

  1. Dans votre fichier app.yaml, définissez la variable d'environnement GAE_USE_SOCKETS_HTTPLIB sur n'importe quelle valeur. La valeur peut être exprimée sous n'importe quelle forme, y compris une chaîne vide. Exemple :

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. Si vous avez activé URL Fetch pour gérer les requêtes envoyées à partir de la bibliothèque requests, vous pouvez supprimer les requêtes AppEngineAdapter de votre application.

    Par exemple, supprimez requests_toolbelt.adapters.appengine de votre fichier appengine_config.py et requests_toolbelt.adapters.appengine.monkeypatch() de vos fichiers Python.

Notez que même si vous contournez la récupération d'URL comme décrit dans les étapes précédentes, votre application peut toujours utiliser directement l'API URL Fetch.

Déployer l'application

Lorsque vous êtes prêt à déployer votre application, vous devez effectuer les opérations suivantes :

  1. Testez l'application sur App Engine.

    Consultez la page "Quotas App Engine" dans Google Cloud Console pour vérifier que votre application n'effectue pas d'appels à l'API URL Fetch.

    Afficher les appels à l'API URL Fetch

  2. Si l'application s'exécute sans erreur, répartissez le trafic pour augmenter lentement le trafic de votre application mise à jour. Surveillez attentivement les éventuels problèmes avant d'acheminer davantage de trafic vers cette application.