I callback consentono alle esecuzioni del flusso di lavoro di attendere che un altro servizio effettui una richiesta all'endpoint di callback. La richiesta riprende l'esecuzione del flusso di lavoro.
Con i callback, puoi segnalare al tuo flusso di lavoro che si è verificato un evento specifico e attendere che si verifichi quell'evento senza eseguire il polling. Ad esempio, puoi creare un flusso di lavoro che ti avvisa quando un prodotto è di nuovo disponibile o quando un articolo è stato spedito oppure che attenda per consentire l'interazione umana, come la revisione di un ordine o la convalida di una traduzione.
Questa pagina mostra come creare un flusso di lavoro che supporti un endpoint di callback e che attenda l'arrivo di richieste HTTP da processi esterni. Puoi anche attendere gli eventi utilizzando callback e trigger Eventarc.
I callback richiedono l'utilizzo di due funzioni integrate della libreria standard:
events.create_callback_endpoint
: crea un endpoint di callback che prevede il metodo HTTP specificatoevents.await_callback
: attende che venga ricevuto un callback all'endpoint specificato
Crea un endpoint che riceve una richiesta di callback
Crea un endpoint di callback che possa ricevere richieste HTTP di arrivo a quell'endpoint.
- Segui i passaggi per creare un nuovo flusso di lavoro o scegli un flusso di lavoro esistente da aggiornare ma non eseguirne ancora il deployment.
- Nella definizione del flusso di lavoro, aggiungi un passaggio per creare un endpoint di callback:
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" } } ]
Sostituisci
METHOD
con il metodo HTTP previsto, uno traGET
,HEAD
,POST
,PUT
,DELETE
,OPTIONS
oPATCH
. Il valore predefinito èPOST
.Il risultato è una mappa,
callback_details
, con un campourl
in cui è archiviato l'URL dell'endpoint creato.L'endpoint di callback è ora pronto a ricevere le richieste in entrata con il metodo HTTP specificato. L'URL dell'endpoint creato può essere utilizzato per attivare il callback da un processo esterno al flusso di lavoro, ad esempio passando l'URL a una Cloud Function.
- Nella definizione del flusso di lavoro, aggiungi un passaggio di attesa di una richiesta di callback:
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" } } ]
Sostituisci
TIMEOUT
con il numero massimo di secondi in cui il flusso di lavoro deve attendere una richiesta. Il valore predefinito è 43200 (12 ore). Se trascorre un determinato periodo di tempo prima della ricezione di una richiesta, viene generato unTimeoutError
.Tieni presente che esiste una durata massima di esecuzione. Per maggiori informazioni, consulta il limite per le richieste.
La mappa
callback_details
del passaggiocreate_callback
precedente viene passata come argomento. - Esegui il deployment del flusso di lavoro per completarne la creazione o l'aggiornamento.
Quando viene ricevuta una richiesta, tutti i dettagli della richiesta vengono archiviati nella mappa
callback_request
. Puoi quindi accedere all'intera richiesta HTTP, compresi l'intestazione, il corpo e una mappaquery
per qualsiasi parametri di ricerca. Ad esempio: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" }
Se il corpo HTTP è testo o JSON, Workflows tenterà di decodificare il corpo, altrimenti vengono restituiti byte non elaborati.
Autorizza le richieste all'endpoint di callback
Per inviare una richiesta a un endpoint di callback, i servizi Google Cloud come
Cloud Run e Cloud Functions, nonché i servizi di terze parti, devono essere autorizzati a farlo disporre delle autorizzazioni
Identity and Access Management (IAM) appropriate; in particolare, workflows.callbacks.send
(incluso nel ruolo Invoker di flussi di lavoro).
Fai una richiesta diretta
Il modo più semplice per creare credenziali di breve durata per un account di servizio è effettuare una richiesta diretta. In questo flusso sono coinvolte due identità: il chiamante e l'account di servizio per il quale viene creata la credenziale. La chiamata al flusso di lavoro di base in questa pagina è un esempio di richiesta diretta. Per ulteriori informazioni, consulta Utilizzare IAM per controllare l'accesso e Autorizzazioni di richiesta diretta.
Generare un token di accesso OAuth 2.0
Per autorizzare un'applicazione a chiamare l'endpoint di callback, puoi generare un
token di accesso OAuth 2.0 per l'account di servizio associato al flusso di lavoro.
Supponendo che tu disponga delle autorizzazioni necessarie (per i ruoli Workflows Editor
o Workflows Admin
e Service Account Token Creator
), puoi anche generare un token eseguendo il metodo generateAccessToken
.
Se la richiesta generateAccessToken
ha esito positivo, il corpo della risposta restituito contiene un token di accesso OAuth 2.0 e una data di scadenza. Per impostazione predefinita, i token di accesso OAuth 2.0 sono validi per un massimo di un'ora. Ad
esempio:
{ "accessToken": "eyJ0eXAi...NiJ9", "expireTime": "2020-04-07T15:01:23.045123456Z" }
accessToken
può quindi essere utilizzato in una chiamata curl all'URL dell'endpoint di callback, come nei seguenti esempi:
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 OAuth per una Cloud Function
Se richiami un callback da una Cloud Function utilizzando lo stesso account di servizio del flusso di lavoro e nello stesso progetto, puoi generare un token di accesso OAuth nella funzione stessa. Ad esempio:
Per saperne di più, consulta il tutorial Creazione di un flusso di lavoro human-in-the-loop utilizzando i callback.
Richiedi accesso offline
I token di accesso scadono periodicamente e diventano credenziali non valide per una richiesta API correlata. Puoi aggiornare un token di accesso senza richiedere l'autorizzazione all'utente, se hai richiesto l'accesso offline agli ambiti associati al token. La richiesta dell'accesso offline è un requisito per qualsiasi applicazione che deve accedere a un'API di Google quando l'utente non è presente. Per maggiori informazioni, consulta Aggiornare un token di accesso (accesso offline).
Richiama un flusso di lavoro esattamente una volta utilizzando i callback
I callback sono completamente idempotenti, il che significa che puoi riprovare a un callback se non riesce senza produrre risultati o effetti collaterali imprevisti.
Dopo aver creato un endpoint di callback, l'URL è pronto a ricevere richieste in arrivo e in genere viene restituito a un chiamante prima che venga effettuata la chiamata corrispondente a await_callback
. Tuttavia, se l'URL di callback deve ancora essere ricevuto quando viene eseguito il passaggio await_callback
, l'esecuzione del flusso di lavoro viene bloccata fino a quando l'endpoint non viene ricevuto (o si verifica un timeout). Una volta ricevuta,
l'esecuzione del flusso di lavoro riprende e il callback viene elaborato.
Dopo aver eseguito il passaggio create_callback_endpoint
e creato un endpoint
di callback, sarà disponibile un singolo slot di callback per il flusso di lavoro. Quando viene ricevuta una richiesta di callback, questo slot viene riempito con il payload di callback fino a quando il callback non può essere elaborato. Quando viene eseguito il passaggio await_callback
, il callback viene elaborato e lo slot viene svuotato e reso disponibile per un altro callback. Puoi quindi riutilizzare l'endpoint di callback e chiamare di nuovo await_callback
.
Se await_callback
viene chiamato una sola volta, ma viene ricevuto un secondo callback, si verifica uno dei seguenti scenari e viene restituito un codice di stato HTTP appropriato:
HTTP
429: Too Many Requests
indica che il primo callback è stato ricevuto correttamente, ma non è stato elaborato; rimane in attesa di elaborazione. Il secondo callback viene rifiutato dal flusso di lavoro.HTTP
200: Success
indica che il primo callback è stato ricevuto correttamente ed è stata restituita una risposta. Il secondo callback viene archiviato e potrebbe non essere mai elaborato a meno cheawait_callback
non venga richiamato una seconda volta. Se il flusso di lavoro termina prima, la seconda richiesta di callback non viene mai elaborata e viene ignorata.HTTP
404: Page Not Found
indica che il flusso di lavoro non è più in esecuzione. Il primo callback è stato elaborato e il flusso di lavoro è stato completato oppure il flusso di lavoro non è riuscito. Per determinarlo, devi eseguire una query sullo stato di esecuzione del flusso di lavoro.
Callback paralleli
Quando i passaggi vengono eseguiti in parallelo e viene creato un callback da un thread padre ed è in attesa nei passaggi figlio, viene seguito lo stesso pattern descritto in precedenza.
Nell'esempio seguente, quando viene eseguito il passaggio create_callback_endpoint
,
viene creato uno slot di callback. Ogni chiamata successiva al numero await_callback
apre
un nuovo spazio di callback. È possibile effettuare dieci callback contemporaneamente, se tutti i thread sono in esecuzione e in attesa prima che venga effettuata una richiesta di callback. Potrebbero essere eseguiti
callback aggiuntivi, ma che verrebbero archiviati e non verranno mai elaborati.
YAML
- createCallbackInParent: call: events.create_callback_endpoint args: http_callback_method: "POST" result: callback_details - parallelStep: parallel: for: range: [1, 10] value: loopValue steps: - waitForCallbackInChild: call: events.await_callback args: callback: ${callback_details}
JSON
[ { "createCallbackInParent": { "call": "events.create_callback_endpoint", "args": { "http_callback_method": "POST" }, "result": "callback_details" } }, { "parallelStep": { "parallel": { "for": { "range": [ 1, 10 ], "value": "loopValue", "steps": [ { "waitForCallbackInChild": { "call": "events.await_callback", "args": { "callback": "${callback_details}" } } } ] } } } } ]
Tieni presente che i callback vengono elaborati nello stesso ordine in cui ogni chiamata viene effettuata da un
ramo a await_callback
. Tuttavia, l'ordine di esecuzione dei rami non è deterministico
e può arrivare a un risultato utilizzando vari percorsi. Per ulteriori informazioni, vedi Passaggi paralleli.
Prova un flusso di lavoro di base per il callback
Puoi creare un flusso di lavoro di base e quindi testare la chiamata all'endpoint di callback di quel flusso di lavoro utilizzando curl. Devi disporre delle autorizzazioni Workflows Editor
o
Workflows Admin
necessarie per il progetto in cui si trova il flusso di lavoro.
-
Crea ed esegui il deployment del flusso di lavoro seguente, quindi 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}" } } ]
Dopo aver eseguito il flusso di lavoro, lo stato dell'esecuzione del flusso di lavoro è
ACTIVE
fino a quando non viene ricevuta la richiesta di callback o non scade il timeout. - Conferma lo stato di esecuzione e recupera l'URL di callback:
Console
-
Nella console Google Cloud, vai alla pagina Flussi di lavoro:
Vai a Workflows -
Fai clic sul nome del flusso di lavoro appena eseguito.
Viene visualizzato lo stato dell'esecuzione del flusso di lavoro.
- Fai clic sulla scheda Log.
Cerca una voce di log simile alla seguente:
Listening for callbacks on https://workflowexecutions.googleapis.com/v1/projects/...
- Copia l'URL di callback da utilizzare nel comando successivo.
gcloud
- Innanzitutto, recupera l'ID esecuzione:
gcloud logging read "Listening for callbacks" --freshness=DURATION
SostituisciDURATION
con un periodo di tempo appropriato per limitare le voci di log restituite (se hai eseguito il flusso di lavoro più volte).Ad esempio,
--freshness=t10m
restituisce voci di log che non risalgono a più di 10 minuti prima. Per maggiori dettagli, vedigcloud topic datetimes
.Viene restituito l'ID esecuzione. Tieni presente che l'URL di callback viene restituito anche nel campo
textPayload
. Copia entrambi i valori da utilizzare nei passaggi seguenti. - Esegui questo comando:
gcloud workflows executions describe WORKFLOW_EXECUTION_ID --workflow=WORKFLOW_NAME
Viene restituito lo stato dell'esecuzione del flusso di lavoro.
-
- Ora puoi chiamare l'endpoint di callback utilizzando un comando curl:
curl -X GET -H "Authorization: Bearer $(gcloud auth print-access-token)" CALLBACK_URL
Tieni presente che per un endpoint
POST
, devi utilizzare un'intestazione di rappresentazioneContent-Type
. Ad esempio:curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" -d '{"foo" : "bar"}' CALLBACK_URL
Sostituisci
CALLBACK_URL
con l'URL copiato nel passaggio precedente. - Tramite la console Google Cloud o Google Cloud CLI, verifica che lo stato dell'esecuzione del flusso di lavoro sia ora
SUCCEEDED
. - Cerca la voce di log con il valore
textPayload
restituito, simile al seguente:Received {"body":null,"headers":...
Esempi
Questi esempi dimostrano la sintassi.
Rileva errori di timeout
Questo esempio si aggiunge all'esempio precedente rilevando eventuali errori di timeout e scrivendoli nel log di 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)}" } } } ] } }
Attendi un callback in un loop di nuovi tentativi
Questo esempio modifica l'esempio precedente implementando un nuovo passaggio. Utilizzando un predicato personalizzato per i nuovi tentativi, il flusso di lavoro registra un avviso quando si verifica un timeout e ripete l'attesa sull'endpoint di callback, fino a cinque volte. Se la quota per i nuovi tentativi è esaurita prima della ricezione del callback, l'errore di timeout finale determina la mancata riuscita del flusso di lavoro.
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 } } ] } }