Cómo se gestionan las solicitudes

ID de región

El REGION_ID es un código abreviado que Google asigna en función de la región que selecciones al crear tu aplicación. El código no corresponde a un país o provincia, aunque algunos IDs de región pueden parecerse a los códigos de país y provincia que se usan habitualmente. En las aplicaciones creadas después de febrero del 2020, REGION_ID.r se incluye en las URLs de App Engine. En las aplicaciones creadas antes de esa fecha, el ID de región es opcional en la URL.

Más información sobre los IDs de región

En este documento se describe cómo recibe solicitudes y envía respuestas tu aplicación de App Engine.

Para obtener más información, consulta la referencia de encabezados de solicitud y respuestas.

Si tu aplicación usa servicios, puedes dirigir las solicitudes a un servicio específico o a una versión específica de ese servicio. Para obtener más información sobre la disponibilidad de las direcciones de servicio, consulta Cómo se enrutan las solicitudes.

Gestionar solicitudes

Tu aplicación se encarga de iniciar un servidor web y de gestionar las solicitudes. Puedes usar cualquier framework web que esté disponible para tu lenguaje de desarrollo.

App Engine ejecuta varias instancias de tu aplicación y cada instancia tiene su propio servidor web para gestionar las solicitudes. Cualquier solicitud se puede enrutar a cualquier instancia, por lo que no es necesario que las solicitudes consecutivas del mismo usuario se envíen a la misma instancia. Una instancia puede gestionar varias solicitudes simultáneamente. El número de instancias se puede ajustar automáticamente a medida que cambia el tráfico. También puedes cambiar el número de solicitudes simultáneas que puede gestionar una instancia definiendo el elemento max_concurrent_requests en el archivo app.yaml.

Cuando App Engine recibe una solicitud web para tu aplicación, llama a la secuencia de comandos del controlador que corresponde a la URL, tal como se describe en el archivo de configuración app.yaml de la aplicación. El tiempo de ejecución de Python 2.7 admite los estándares WSGI y CGI para ofrecer compatibilidad con versiones anteriores. Se recomienda usar WSGI, ya que algunas funciones de Python 2.7 no funcionan sin él. La configuración de los controladores de secuencias de comandos de tu aplicación determina si una solicitud se gestiona mediante WSGI o CGI.

La siguiente secuencia de comandos de Python responde a una solicitud con un encabezado HTTP y el mensaje Hello, World!.

import webapp2


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/plain"
        self.response.write("Hello, World!")


app = webapp2.WSGIApplication(
    [
        ("/", MainPage),
    ],
    debug=True,
)

Para enviar varias solicitudes a cada servidor web en paralelo, marca tu aplicación como segura para subprocesos añadiendo threadsafe: true a tu archivo app.yaml. Las solicitudes simultáneas no están disponibles si algún controlador de secuencias de comandos usa CGI.

Cuotas y límites

App Engine asigna automáticamente recursos a tu aplicación a medida que aumenta el tráfico. Sin embargo, está sujeta a las siguientes restricciones:

  • App Engine reserva capacidad de escalado automático para las aplicaciones con baja latencia, en las que la aplicación responde a las solicitudes en menos de un segundo.

  • Las aplicaciones que dependen mucho de la CPU también pueden incurrir en una latencia adicional para compartir recursos de forma eficiente con otras aplicaciones en los mismos servidores. Las solicitudes de archivos estáticos están exentas de estos límites de latencia.

Cada solicitud entrante a la aplicación se incluye en el límite de solicitudes. Los datos enviados en respuesta a una solicitud se tienen en cuenta para el límite de ancho de banda saliente (facturable).

Tanto las solicitudes HTTP como las HTTPS (seguras) se contabilizan en los límites de Solicitudes, Ancho de banda de entrada (facturable) y Ancho de banda de salida (facturable). La página Detalles de la cuota de la Google Cloud consola también muestra los valores de Solicitudes seguras, Ancho de banda de entrada seguro y Ancho de banda de salida seguro por separado a modo informativo. Solo las solicitudes HTTPS se contabilizan en estos valores. Para obtener más información, consulta la página Cuotas.

Los siguientes límites se aplican específicamente al uso de controladores de solicitudes:

Límite Cantidad
Tamaño de la solicitud 32 megabytes
Tamaño de la respuesta 32 megabytes
Tiempo de espera de solicitud Depende del tipo de escalado que use tu aplicación
Número total máximo de archivos (archivos de aplicaciones y archivos estáticos) 10.000 en total
1000 por directorio
Tamaño máximo de un archivo de aplicación 32 megabytes
Tamaño máximo de un archivo estático 32 megabytes
Tamaño total máximo de todos los archivos estáticos y de la aplicación El primer gigabyte es gratuito
0,026 USD por gigabyte al mes después del primer gigabyte
Tiempo de espera de las solicitudes pendientes 10 segundos
Tamaño máximo de un único campo de encabezado de solicitud 8 kilobytes para los runtimes de segunda generación en el entorno estándar. Las solicitudes a estos tiempos de ejecución con campos de encabezado que superen los 8 kilobytes devolverán errores HTTP 400.

Límites de solicitudes

Todas las solicitudes HTTP/2 se traducirán a solicitudes HTTP/1.1 cuando se reenvíen al servidor de aplicaciones.

Límites de respuesta

  • Las respuestas dinámicas tienen un límite de 32 MB. Si un controlador de secuencias de comandos genera una respuesta que supera este límite, el servidor devuelve una respuesta vacía con el código de estado 500 Internal Server Error. Esta limitación no se aplica a las respuestas que proporcionan datos del antiguo Blobstore o de Cloud Storage.

  • El límite de encabezados de respuesta es de 8 KB para los runtimes de segunda generación. Los encabezados de respuesta que superen este límite devolverán errores HTTP 502 y los registros mostrarán upstream sent too big header while reading response header from upstream.

Encabezados de solicitud

Una solicitud HTTP entrante incluye las cabeceras HTTP enviadas por el cliente. Por motivos de seguridad, los proxies intermedios anonimizan o modifican algunos encabezados antes de que lleguen a la aplicación.

Para obtener más información, consulta la referencia de encabezados de solicitud.

Gestionar los tiempos de espera de las solicitudes

App Engine está optimizado para aplicaciones con solicitudes de corta duración, normalmente las que tardan unos cientos de milisegundos. Una aplicación eficiente responde rápidamente a la mayoría de las solicitudes. Una aplicación que no lo haga no se escalará bien con la infraestructura de App Engine. Para asegurar este nivel de rendimiento, el sistema impone un tiempo de espera máximo de las solicitudes al que deben responder todas las aplicaciones.

Si tu aplicación supera este plazo, App Engine interrumpe el controlador de solicitudes. El entorno de ejecución de Python lo consigue generando una excepción DeadlineExceededError desde google.appengine.runtime. Si el controlador de solicitudes no detecta esta excepción, como ocurre con todas las excepciones no detectadas, el entorno de tiempo de ejecución devolverá un error de servidor HTTP 500 al cliente.

El controlador de solicitudes puede detectar este error para personalizar la respuesta. El entorno de tiempo de ejecución da al controlador de solicitudes un poco más de tiempo (menos de un segundo) después de generar la excepción para preparar una respuesta personalizada.

class TimerHandler(webapp2.RequestHandler):
    def get(self):
        from google.appengine.runtime import DeadlineExceededError

        try:
            time.sleep(70)
            self.response.write("Completed.")
        except DeadlineExceededError:
            self.response.clear()
            self.response.set_status(500)
            self.response.out.write("The request did not complete in time.")

Si el controlador no ha devuelto una respuesta o ha generado una excepción antes de la segunda fecha límite, se detiene y se devuelve una respuesta de error predeterminada.

Respuestas

App Engine llama a la secuencia de comandos del controlador con un Request y espera a que la secuencia de comandos devuelva datos. Todos los datos escritos en el flujo de salida estándar se envían como respuesta HTTP.

Hay límites de tamaño que se aplican a la respuesta que generas, y la respuesta se puede modificar antes de devolverse al cliente.

Para obtener más información, consulta la referencia de respuestas de solicitudes.

Respuestas de streaming

App Engine no admite respuestas de streaming en las que los datos se envían en fragmentos incrementales al cliente mientras se procesa una solicitud. Todos los datos de tu código se recogen tal como se ha descrito anteriormente y se envían como una única respuesta HTTP.

Compresión de respuestas

App Engine hace todo lo posible para servir contenido comprimido (gzipeado) a los clientes que lo admiten. Para determinar si se debe comprimir el contenido, App Engine hace lo siguiente cuando recibe una solicitud:

  1. Confirma si el cliente puede recibir respuestas comprimidas de forma fiable consultando los encabezados Accept-Encoding y User-Agent de la solicitud. De esta forma, se evitan algunos errores conocidos con el contenido comprimido con gzip en navegadores populares.

  2. Confirma si es adecuado comprimir el contenido consultando el encabezado Content-Type que has configurado para el controlador de respuestas. Por lo general, la compresión es adecuada para los tipos de contenido basados en texto, pero no para los tipos de contenido binario.

Ten en cuenta lo siguiente:

  • Un cliente puede forzar la compresión de los tipos de contenido basados en texto si asigna el valor gzip a los encabezados de solicitud Accept-Encoding y User-Agent.

  • Si una solicitud no especifica gzip en el encabezado Accept-Encoding, App Engine no comprimirá los datos de la respuesta.

  • El frontend de Google almacena en caché las respuestas de los controladores de archivos estáticos y directorios de App Engine. En función de varios factores, como el tipo de datos de respuesta que se almacena en caché primero, los encabezados Vary que hayas especificado en la respuesta y los encabezados que se incluyan en la solicitud, un cliente podría solicitar datos comprimidos, pero recibir datos sin comprimir, y viceversa. Para obtener más información, consulta Almacenamiento en caché de respuestas.

Almacenamiento en caché de respuestas

El frontend de Google, y posiblemente el navegador del usuario y otros servidores proxy de almacenamiento en caché intermedios, almacenarán en caché las respuestas de tu aplicación según las instrucciones de los encabezados de almacenamiento en caché estándar que especifiques en la respuesta. Puede especificar estos encabezados de respuesta a través de su framework, directamente en su código o mediante los gestores de archivos y directorios estáticos de App Engine.

En el frontend de Google, la clave de caché es la URL completa de la solicitud.

Almacenar contenido estático en caché

Para asegurarte de que los clientes siempre reciban contenido estático actualizado en cuanto se publique, te recomendamos que sirvas contenido estático desde directorios con versiones, como css/v1/styles.css. El frontend de Google no validará la caché (comprobará si hay contenido actualizado) hasta que caduque. Aunque la caché caduque, no se actualizará hasta que cambie el contenido de la URL de la solicitud.

Los siguientes encabezados de respuesta que puedes definir en app.yaml influyen en cómo y cuándo almacena en caché el contenido el frontend de Google:

  • Cache-Control debe tener el valor public para que Google Frontend pueda almacenar contenido en caché. También puede almacenarse en caché en Google Frontend a menos que especifiques una directiva Cache-Control private o no-store. Si no defines este encabezado en app.yaml , App Engine lo añade automáticamente a todas las respuestas gestionadas por un controlador de archivos o directorios estáticos. Para obtener más información, consulta el artículo sobre encabezados añadidos o sustituidos.

  • Vary: para que la caché devuelva respuestas diferentes para una URL en función de los encabezados que se envíen en la solicitud, defina uno o varios de los siguientes valores en el encabezado de respuesta Vary: Accept, Accept-Encoding, Origin o X-Origin.

    Debido a la alta cardinalidad potencial, los datos no se almacenarán en caché para otros valores de Vary.

    Por ejemplo:

    1. Especifica el siguiente encabezado de respuesta:

      Vary: Accept-Encoding

    2. Tu aplicación recibe una solicitud que contiene el encabezado Accept-Encoding: gzip. App Engine devuelve una respuesta comprimida y el frontend de Google almacena en caché la versión comprimida con gzip de los datos de la respuesta. Todas las solicitudes posteriores de esta URL que contengan el encabezado Accept-Encoding: gzip recibirán los datos comprimidos con gzip de la caché hasta que esta se invalide (porque el contenido cambie después de que caduque la caché).

    3. Tu aplicación recibe una solicitud que no contiene el encabezado Accept-Encoding. App Engine devuelve una respuesta sin comprimir y Google Frontend almacena en caché la versión sin comprimir de los datos de la respuesta. Todas las solicitudes posteriores de esta URL que no contengan el encabezado Accept-Encoding recibirán los datos comprimidos de la caché hasta que esta se invalide.

    Si no especifica un encabezado de respuesta Vary, el frontend de Google crea una sola entrada de caché para la URL y la usará en todas las solicitudes, independientemente de los encabezados de la solicitud. Por ejemplo:

    1. No especifica el encabezado de respuesta Vary: Accept-Encoding.
    2. Una solicitud contiene la cabecera Accept-Encoding: gzip y se almacenará en caché la versión comprimida con gzip de los datos de la respuesta.
    3. Una segunda solicitud no contiene el encabezado Accept-Encoding: gzip. Sin embargo, como la caché contiene una versión comprimida con gzip de los datos de respuesta, la respuesta se comprimirá con gzip aunque el cliente haya solicitado datos sin comprimir.

Los encabezados de la solicitud también influyen en el almacenamiento en caché:

  • Si la solicitud contiene un encabezado Authorization, el contenido no se almacenará en la caché del frontend de Google.

Vencimiento de la caché

De forma predeterminada, los encabezados de almacenamiento en caché que los controladores de archivos estáticos y de directorios de App Engine añaden a las respuestas indican a los clientes y a los proxies web, como Google Frontend, que el caché caduque al cabo de 10 minutos.

Una vez que se ha transmitido un archivo con un tiempo de vencimiento determinado, por lo general, no hay forma de eliminarlo de las cachés de proxy web, aunque el usuario borre su propia caché del navegador. Al volver a implementar una nueva versión de la aplicación, no se restablecerá ninguna caché. Por lo tanto, si tienes previsto modificar un archivo estático, debe tener un tiempo de vencimiento corto (menos de una hora). En la mayoría de los casos, el tiempo de vencimiento predeterminado de 10 minutos es adecuado.

Puedes cambiar el vencimiento predeterminado de todos los controladores de archivos estáticos y directorios especificando el elemento default_expiration en el archivo app.yaml. Para definir tiempos de caducidad específicos para controladores individuales, especifica el elemento expiration en el elemento del controlador de tu archivo app.yaml.

El valor que especifiques en el tiempo de los elementos de vencimiento se usará para definir los encabezados de respuesta HTTP Cache-Control y Expires.

Almacenamiento en caché de aplicaciones

El entorno de ejecución de Python almacena en caché los módulos importados entre solicitudes en un único servidor web, de forma similar a como una aplicación de Python independiente carga un módulo solo una vez, aunque varios archivos importen el módulo. Como los controladores WSGI son módulos, se almacenan en caché entre solicitudes. Las secuencias de comandos del controlador de CGI solo se almacenan en caché si proporcionan una rutina main(). De lo contrario, se cargan en cada solicitud.

El almacenamiento en caché de aplicaciones proporciona una ventaja significativa en lo que respecta al tiempo de respuesta. Te recomendamos que todos los scripts de controlador CGI usen una rutina main(), tal como se describe a continuación.

Las importaciones se almacenan en caché

Para mejorar la eficiencia, el servidor web mantiene los módulos importados en la memoria y no los vuelve a cargar ni a evaluar en las solicitudes posteriores a la misma aplicación en el mismo servidor. La mayoría de los módulos no inicializan ningún dato global ni tienen otros efectos secundarios cuando se importan, por lo que almacenarlos en caché no cambia el comportamiento de la aplicación.

Si tu aplicación importa un módulo que depende del módulo que se está evaluando para cada solicitud, la aplicación debe adaptarse a este comportamiento de almacenamiento en caché.

Almacenamiento en caché de controladores CGI

Puedes indicar a App Engine que almacene en caché el propio script del controlador CGI, además de los módulos importados. Si la secuencia de comandos del controlador define una función llamada main(), la secuencia de comandos y su entorno global se almacenarán en caché como un módulo importado. La primera solicitud de la secuencia de comandos en un servidor web determinado evalúa la secuencia de comandos de forma normal. En las solicitudes posteriores, App Engine llama a la función main() en el entorno almacenado en caché.

Para almacenar en caché una secuencia de comandos de controlador, App Engine debe poder llamar a main() sin argumentos. Si la secuencia de comandos del controlador no define una función main() o la función main() requiere argumentos (que no tienen valores predeterminados), App Engine carga y evalúa toda la secuencia de comandos en cada solicitud.

Mantener el código Python analizado en la memoria ahorra tiempo y permite obtener respuestas más rápidas. El almacenamiento en caché del entorno global presenta otros posibles usos:

  • Expresiones regulares compiladas. Todas las expresiones regulares se analizan y se almacenan en formato compilado. Puedes almacenar expresiones regulares compiladas en variables globales y, a continuación, usar el almacenamiento en caché de la aplicación para reutilizar los objetos compilados entre solicitudes.

  • Objetos GqlQuery. La cadena de consulta GQL se analiza cuando se crea el objeto GqlQuery. Reutilizar un objeto GqlQuery con la vinculación de parámetros y el método bind() es más rápido que volver a crear el objeto cada vez. Puede almacenar un objeto GqlQuery con la vinculación de parámetros para los valores en una variable global y, a continuación, reutilizarlo vinculando nuevos valores de parámetros para cada solicitud.

  • Archivos de datos y de configuración. Si tu aplicación carga y analiza datos de configuración de un archivo, puede conservar los datos analizados en la memoria para no tener que volver a cargar el archivo con cada solicitud.

La secuencia de comandos del controlador debe llamar a main() cuando se importe. App Engine espera que la importación de la secuencia de comandos llame a main(), por lo que App Engine no la llama al cargar el controlador de solicitudes por primera vez en un servidor.

El almacenamiento en caché de aplicaciones con main() mejora significativamente el tiempo de respuesta de tu controlador CGI. Lo recomendamos para todas las aplicaciones que usen CGI.

Almacenamiento de registros

El servidor web de App Engine registra todo lo que escribe la secuencia de comandos del controlador en el flujo de salida estándar para responder a la solicitud web. También captura todo lo que el script del controlador escribe en el flujo de errores estándar y lo almacena como datos de registro. A cada solicitud se le asigna un request_id, un identificador único global basado en la hora de inicio de la solicitud. Los datos de registro de tu aplicación se pueden ver en la Google Cloud consola mediante Cloud Logging.

El entorno de ejecución de Python de App Engine incluye compatibilidad especial con el módulo de registro de la biblioteca estándar de Python para comprender conceptos de registro, como los niveles de registro ("debug", "info", "warning", "error" y "critical").

import logging

import webapp2


class MainPage(webapp2.RequestHandler):
    def get(self):
        logging.debug("This is a debug message")
        logging.info("This is an info message")
        logging.warning("This is a warning message")
        logging.error("This is an error message")
        logging.critical("This is a critical message")

        try:
            raise ValueError("This is a sample value error.")
        except ValueError:
            logging.exception("A example exception log.")

        self.response.out.write("Logging example.")


app = webapp2.WSGIApplication([("/", MainPage)], debug=True)

El entorno

El entorno de ejecución define automáticamente varias variables de entorno. Puedes definir más en app.yaml. De las variables definidas automáticamente, algunas son especiales para App Engine, mientras que otras forman parte de los estándares WSGI o CGI. El código de Python puede acceder a estas variables mediante el diccionario os.environ.

Las siguientes variables de entorno son específicas de App Engine:

  • CURRENT_VERSION_ID: la versión principal y secundaria de la aplicación que se está ejecutando, con el formato "X.Y". El número de versión principal ("X") se especifica en el archivo app.yaml de la aplicación. El número de versión secundaria ("Y") se asigna automáticamente cuando se sube cada versión de la aplicación a App Engine. En el servidor web de desarrollo, la versión secundaria siempre es "1".

  • AUTH_DOMAIN: el dominio que se usa para autenticar a los usuarios con la API Users. Las aplicaciones alojadas en appspot.com tienen un AUTH_DOMAIN de gmail.com y aceptan cualquier cuenta de Google. Las aplicaciones alojadas en un dominio personalizado tienen un AUTH_DOMAIN igual al dominio personalizado.

  • INSTANCE_ID: contiene el ID de instancia de la instancia de frontend que gestiona una solicitud. El ID es una cadena hexadecimal (por ejemplo, 00c61b117c7f7fd0ce9e1325a04b8f0df30deaaf). Un administrador que haya iniciado sesión puede usar el ID en una URL: https://INSTANCE_ID-dot-VERSION_ID-dot-SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com. La solicitud se dirigirá a esa instancia frontend específica. Si la instancia no puede gestionar la solicitud, devuelve un error 503 inmediatamente.

Las siguientes variables de entorno forman parte de los estándares WSGI y CGI, con un comportamiento especial en App Engine:

  • SERVER_SOFTWARE: En el servidor web de desarrollo, este valor es "Development/X.Y", donde "X.Y" es la versión del tiempo de ejecución. Cuando se ejecuta en App Engine, este valor es "Google App Engine/X.Y.Z".

Se definen variables de entorno adicionales según el estándar WSGI o CGI. Para obtener más información sobre estas variables, consulta el estándar WSGI o el estándar CGI, según corresponda.

También puedes definir variables de entorno en el app.yaml archivo:

env_variables:
  DJANGO_SETTINGS_MODULE: 'myapp.settings'

El siguiente controlador de solicitudes de webapp2 muestra todas las variables de entorno visibles para la aplicación en el navegador:

class PrintEnvironmentHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/plain"
        for key, value in os.environ.iteritems():
            self.response.out.write("{} = {}\n".format(key, value))

IDs de solicitud

En el momento de la solicitud, puedes guardar el ID de solicitud, que es único para esa solicitud. El ID de solicitud se puede usar más adelante para buscar los registros de esa solicitud en Cloud Logging.

En el siguiente código de ejemplo se muestra cómo obtener el ID de solicitud en el contexto de una solicitud:

class RequestIdHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/plain"
        request_id = os.environ.get("REQUEST_LOG_ID")
        self.response.write("REQUEST_LOG_ID={}".format(request_id))

Forzar las conexiones HTTPS

Por motivos de seguridad, todas las aplicaciones deben animar a los clientes a conectarse a través de https. Para indicar al navegador que prefiera https en lugar de http en una página o en todo un dominio, define el encabezado Strict-Transport-Security en tus respuestas. Por ejemplo:

Strict-Transport-Security: max-age=31536000; includeSubDomains
Para definir este encabezado en cualquier contenido estático que sirva tu aplicación, añade el encabezado a los gestores de archivos y directorios estáticos de tu aplicación.

Para definir este encabezado en las respuestas que se generen a partir de tu código, usa la biblioteca flask-talisman.

Gestionar el trabajo asíncrono en segundo plano

El trabajo en segundo plano es cualquier tarea que realiza tu aplicación para una solicitud después de haber enviado la respuesta HTTP. Evita realizar tareas en segundo plano en tu aplicación y revisa el código para asegurarte de que todas las operaciones asíncronas finalicen antes de enviar la respuesta.

Para los trabajos de larga duración, recomendamos usar Cloud Tasks. Con Cloud Tasks, las solicitudes HTTP son duraderas y solo devuelven una respuesta cuando finaliza el trabajo asíncrono.