Las devoluciones de llamada permiten que las ejecuciones de flujo de trabajo esperen a que otro servicio envíe una solicitud al extremo de devolución de llamada. Esa solicitud reanuda la ejecución del flujo de trabajo.
Con las devoluciones de llamada, puedes indicar a tu flujo de trabajo que se produjo un evento específico y esperar ese evento sin sondear. Por ejemplo, puedes crear un flujo de trabajo que te notifique cuando un producto vuelva a estar en stock o cuando se haya enviado un artículo, o que espere para permitir la interacción humana, como revisar un pedido o validar una traducción.
En esta página, se muestra cómo crear un flujo de trabajo que admita un extremo de devolución de llamada y que espere a que las solicitudes HTTP de procesos externos lleguen a ese extremo. También puedes esperar a que ocurran eventos con devoluciones de llamada y activadores de Eventarc.
Las devoluciones de llamada requieren el uso de dos funciones integradas en la biblioteca estándar:
events.create_callback_endpoint
: Crea un extremo de devolución de llamada que espera el método HTTP especificado.events.await_callback
: Espera a que se reciba una devolución de llamada en el extremo determinado.
Crea un extremo que reciba una solicitud de devolución de llamada
Crea un extremo de devolución de llamada que pueda recibir solicitudes HTTP para llegar a ese extremo.
- Sigue los pasos para crear un flujo de trabajo nuevo o elegir uno existente para actualizar, pero aún no lo implementes.
- En la definición del flujo de trabajo, agrega un paso para crear un extremo de devolución de llamada:
YAML
- create_callback: call: events.create_callback_endpoint args: http_callback_method: "METHOD" result: callback_details
JSON
[ { "create_callback": { "call": "events.create_callback_endpoint", "args": { "http_callback_method": "METHOD" }, "result": "callback_details" } } ]
Reemplaza
METHOD
por el método HTTP esperado, comoGET
,HEAD
,POST
,PUT
,DELETE
,OPTIONS
oPATCH
. El valor predeterminado esPOST
.El resultado es un mapa,
callback_details
, con un campourl
que almacena la URL del extremo creado.El extremo de devolución de llamada ahora está listo para recibir solicitudes entrantes con el método HTTP especificado. La URL del extremo creado se puede usar para activar la devolución de llamada desde un proceso externo al flujo de trabajo; por ejemplo, cuando se pasa la URL a una Cloud Function.
- En la definición del flujo de trabajo, agrega un paso para esperar una solicitud de devolución de llamada:
YAML
- await_callback: call: events.await_callback args: callback: ${callback_details} timeout: TIMEOUT result: callback_request
JSON
[ { "await_callback": { "call": "events.await_callback", "args": { "callback": "${callback_details}", "timeout": TIMEOUT }, "result": "callback_request" } } ]
Reemplaza
TIMEOUT
por la cantidad máxima de segundos que el flujo de trabajo debe esperar una solicitud. El valor predeterminado es 43,200 (12 horas). Si transcurre el tiempo antes de que se reciba una solicitud, se genera unaTimeoutError
.Ten en cuenta que hay una duración máxima de ejecución. Para obtener más información, consulta el límite de solicitudes.
El mapa
callback_details
del pasocreate_callback
anterior se pasa como argumento. - Implementa tu flujo de trabajo para terminar de crearlo o actualizarlo.
Cuando se recibe una solicitud, todos los detalles de esta se almacenan en el mapa
callback_request
. Luego, tienes acceso a toda la solicitud HTTP, incluido su encabezado, cuerpo y un mapa dequery
para cualquier parámetro de consulta. Por ejemplo:YAML
http_request: body: headers: {...} method: GET query: {} url: "/v1/projects/350446661175/locations/us-central1/workflows/workflow-1/executions/46804f42-dc83-46d6-87e4-93962866ed81/callbacks/49c80102-74d2-49cd-a70e-805a9fded94f_2de9b413-6332-412d-99c3-d7e9b6eeeda2" received_time: 2021-06-24 12:49:16.988072651 -0700 PDT m=+742581.005780667 type: HTTP
JSON
{ "http_request":{ "body":null, "headers":{ ... }, "method":"GET", "query":{ }, "url":"/v1/projects/350446661175/locations/us-central1/workflows/workflow-1/executions/46804f42-dc83-46d6-87e4-93962866ed81/callbacks/49c80102-74d2-49cd-a70e-805a9fded94f_2de9b413-6332-412d-99c3-d7e9b6eeeda2" }, "received_time":"2021-06-24 12:49:16.988072651 -0700 PDT m=+742581.005780667", "type":"HTTP" }
Si el cuerpo HTTP es texto o JSON, Workflows intentará decodificar el cuerpo; de lo contrario, se mostrarán bytes sin procesar.
Autoriza las solicitudes al extremo de devolución de llamada
Para enviar una solicitud a un extremo de devolución de llamada, los servicios de Google Cloud, como
Cloud Run y Cloud Functions, al igual que los servicios
de terceros, deben estar autorizados para hacerlo con los permisos
de Identity and Access Management (IAM) adecuados; en particular, workflows.callbacks.send
(incluido en el rol de Invocador de flujos de trabajo).
Cómo realizar una solicitud directa
La forma más sencilla de crear credenciales de corta duración para una cuenta de servicio es realizar una solicitud directa. En este flujo, participan dos identidades: el emisor y la cuenta de servicio para la que se crea la credencial. La llamada al flujo de trabajo básico en esta página es un ejemplo de una solicitud directa. Si deseas obtener más información, consulta Usa IAM para controlar el acceso y Permisos de solicitud directa.
Genera un token de acceso de OAuth 2.0
Para autorizar que una aplicación llame al extremo de devolución de llamada, puedes generar un token de acceso de OAuth 2.0 para la cuenta de servicio asociada con el flujo de trabajo.
Si suponemos que tienes los permisos necesarios (para las funciones Workflows Editor
, Workflows Admin
y Service Account Token Creator
), también puedes generar un token por tu cuenta ejecutando el método generateAccessToken
.
Si la solicitud generateAccessToken
se realiza correctamente, el cuerpo de la respuesta que se muestra contendrá un token de acceso de OAuth 2.0 y una fecha de vencimiento. (de forma predeterminada, los tokens de acceso de OAuth 2.0 son válidos durante un máximo de 1 hora). Por ejemplo:
{ "accessToken": "eyJ0eXAi...NiJ9", "expireTime": "2020-04-07T15:01:23.045123456Z" }
accessToken
se puede usar en una llamada curl a la URL del extremo de devolución de llamada, como en los siguientes ejemplos:
curl -X GET -H "Authorization: Bearer ACCESS_TOKEN_STRING" CALLBACK_URL
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ACCESS_TOKEN_STRING" -d '{"foo" : "bar"}' CALLBACK_URL
Genera un token de OAuth para una Cloud Function
Si invocas una devolución de llamada desde una Cloud Function con la misma cuenta de servicio que el flujo de trabajo y en el mismo proyecto, puedes generar un token de acceso de OAuth en la función. Por ejemplo:
Para obtener más contexto, consulta el instructivo Crea un flujo de trabajo con interacción humana mediante devoluciones de llamada.
Solicitar acceso sin conexión
Los tokens de acceso vencen periódicamente y se convierten en credenciales no válidas para una solicitud a la API relacionada. Puedes actualizar un token de acceso sin solicitarle permiso al usuario si solicitaste acceso sin conexión a los permisos asociados con el token. Solicitar acceso sin conexión es un requisito para cualquier aplicación que necesite acceder a una API de Google cuando el usuario no esté presente. Para obtener más información, consulta Actualiza un token de acceso (acceso sin conexión).
Prueba un flujo de trabajo básico de devolución de llamada
Puedes crear un flujo de trabajo básico y, luego, probar la llamada al extremo de devolución de llamada de ese flujo de trabajo con curl. Debes tener los permisos Workflows Editor
o Workflows Admin
necesarios para el proyecto en el que reside el flujo de trabajo.
-
Crea e implementa el siguiente flujo de trabajo y, luego,
execute.
YAML
- create_callback: call: events.create_callback_endpoint args: http_callback_method: "GET" result: callback_details - print_callback_details: call: sys.log args: severity: "INFO" text: ${"Listening for callbacks on " + callback_details.url} - await_callback: call: events.await_callback args: callback: ${callback_details} timeout: 3600 result: callback_request - print_callback_request: call: sys.log args: severity: "INFO" text: ${"Received " + json.encode_to_string(callback_request.http_request)} - return_callback_result: return: ${callback_request.http_request}
JSON
[ { "create_callback": { "call": "events.create_callback_endpoint", "args": { "http_callback_method": "GET" }, "result": "callback_details" } }, { "print_callback_details": { "call": "sys.log", "args": { "severity": "INFO", "text": "${\"Listening for callbacks on \" + callback_details.url}" } } }, { "await_callback": { "call": "events.await_callback", "args": { "callback": "${callback_details}", "timeout": 3600 }, "result": "callback_request" } }, { "print_callback_request": { "call": "sys.log", "args": { "severity": "INFO", "text": "${\\"Received \\" + json.encode_to_string(callback_request.http_request)}" } } }, { "return_callback_result": { "return": "${callback_request.http_request}" } } ]
Después de ejecutar el flujo de trabajo, su estado será
ACTIVE
hasta que se reciba la solicitud de devolución de llamada o se agote el tiempo de espera. - Confirma el estado de ejecución y recupera la URL de devolución de llamada:
Console
-
En la consola de Google Cloud, ve a la página Flujos de trabajo:
Ir a Workflows -
Haz clic en el nombre del flujo de trabajo que acabas de ejecutar.
Se muestra el estado de la ejecución del flujo de trabajo.
- Haz clic en la pestaña Registros.
Busca una entrada de registro similar a la que se muestra a continuación:
Listening for callbacks on https://workflowexecutions.googleapis.com/v1/projects/...
- Copia la URL de devolución de llamada para usarla en el siguiente comando.
gcloud
- Primero, recupera el ID de ejecución:
gcloud logging read "Listening for callbacks" --freshness=DURATION
ReemplazaDURATION
por una cantidad de tiempo adecuada para limitar las entradas de registro que se muestran (si ejecutaste el flujo de trabajo varias veces).Por ejemplo,
--freshness=t10m
muestra entradas de registro que no tienen más de 10 minutos. Para obtener más información, consultagcloud topic datetimes
.Se muestra el ID de ejecución. Ten en cuenta que la URL de devolución de llamada también se muestra en el campo
textPayload
. Copia ambos valores para usarlos en los siguientes pasos. - Ejecuta el siguiente comando:
-
- Ahora puedes llamar al extremo de devolución de llamada con un comando curl:
curl -X GET -H "Authorization: Bearer $(gcloud auth print-access-token)" CALLBACK_URL
Ten en cuenta que, para un extremo
POST
, debes usar un encabezado de representaciónContent-Type
. Por ejemplo:curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" -d '{"foo" : "bar"}' CALLBACK_URL
Reemplaza
CALLBACK_URL
por la URL que copiaste en el paso anterior. - A través de la consola de Google Cloud o con Google Cloud CLI, confirma que el estado de la ejecución del flujo de trabajo ahora sea
SUCCEEDED
. - Busca la entrada de registro con el
textPayload
que se muestra, que se parece al siguiente:Received {"body":null,"headers":...
Muestras
Estos ejemplos demuestran la sintaxis.
Errores de tiempo de espera de captura
Esta muestra se agrega a la muestra anterior mediante la captura de cualquier error de tiempo de espera y la escritura de los errores en el registro del sistema.
YAML
main: steps: - create_callback: call: events.create_callback_endpoint args: http_callback_method: "GET" result: callback_details - print_callback_details: call: sys.log args: severity: "INFO" text: ${"Listening for callbacks on " + callback_details.url} - await_callback: try: call: events.await_callback args: callback: ${callback_details} timeout: 3600 result: callback_request except: as: e steps: - log_error: call: sys.log args: severity: "ERROR" text: ${"Received error " + e.message} next: end - print_callback_result: call: sys.log args: severity: "INFO" text: ${"Received " + json.encode_to_string(callback_request.http_request)}
JSON
{ "main": { "steps": [ { "create_callback": { "call": "events.create_callback_endpoint", "args": { "http_callback_method": "GET" }, "result": "callback_details" } }, { "print_callback_details": { "call": "sys.log", "args": { "severity": "INFO", "text": "${\"Listening for callbacks on \" + callback_details.url}" } } }, { "await_callback": { "try": { "call": "events.await_callback", "args": { "callback": "${callback_details}", "timeout": 3600 }, "result": "callback_request" }, "except": { "as": "e", "steps": [ { "log_error": { "call": "sys.log", "args": { "severity": "ERROR", "text": "${\"Received error \" + e.message}" }, "next": "end" } } ] } } }, { "print_callback_result": { "call": "sys.log", "args": { "severity": "INFO", "text": "${\"Received \" + json.encode_to_string(callback_request.http_request)}" } } } ] } }
Espera una devolución de llamada en un bucle de reintentos
En esta muestra, se modifica la muestra anterior mediante la implementación de un paso de reintento. Con un predicado de reintento personalizado, el flujo de trabajo registra una advertencia cuando se agota un tiempo de espera y, luego, vuelve a intentar la espera en el extremo de devolución de llamada, hasta cinco veces. Si la cuota de reintentos se agota antes de que se reciba la devolución de llamada, el error de tiempo de espera final hace que el flujo de trabajo falle.
YAML
main: steps: - create_callback: call: events.create_callback_endpoint args: http_callback_method: "GET" result: callback_details - print_callback_details: call: sys.log args: severity: "INFO" text: ${"Listening for callbacks on " + callback_details.url} - await_callback: try: call: events.await_callback args: callback: ${callback_details} timeout: 60.0 result: callback_request retry: predicate: ${log_timeout} max_retries: 5 backoff: initial_delay: 1 max_delay: 10 multiplier: 2 - print_callback_result: call: sys.log args: severity: "INFO" text: ${"Received " + json.encode_to_string(callback_request.http_request)} log_timeout: params: [e] steps: - when_to_repeat: switch: - condition: ${"TimeoutError" in e.tags} steps: - log_error_and_retry: call: sys.log args: severity: "WARNING" text: "Timed out waiting for callback, retrying" - exit_predicate: return: true - otherwise: return: false
JSON
{ "main": { "steps": [ { "create_callback": { "call": "events.create_callback_endpoint", "args": { "http_callback_method": "GET" }, "result": "callback_details" } }, { "print_callback_details": { "call": "sys.log", "args": { "severity": "INFO", "text": "${\"Listening for callbacks on \" + callback_details.url}" } } }, { "await_callback": { "try": { "call": "events.await_callback", "args": { "callback": "${callback_details}", "timeout": 60 }, "result": "callback_request" }, "retry": { "predicate": "${log_timeout}", "max_retries": 5, "backoff": { "initial_delay": 1, "max_delay": 10, "multiplier": 2 } } } }, { "print_callback_result": { "call": "sys.log", "args": { "severity": "INFO", "text": "${\"Received \" + json.encode_to_string(callback_request.http_request)}" } } } ] }, "log_timeout": { "params": [ "e" ], "steps": [ { "when_to_repeat": { "switch": [ { "condition": "${\"TimeoutError\" in e.tags}", "steps": [ { "log_error_and_retry": { "call": "sys.log", "args": { "severity": "WARNING", "text": "Timed out waiting for callback, retrying" } } }, { "exit_predicate": { "return": true } } ] } ] } }, { "otherwise": { "return": false } } ] } }