Gestori personalizzati di Funzioni di Azure
Ogni app per le funzioni viene eseguita da un gestore specifico del linguaggio. Anche se Funzioni di Azure funzionalità di molti gestori di linguaggio per impostazione predefinita, esistono casi in cui è possibile usare altri linguaggi o runtime.
I gestori personalizzati sono server Web leggeri che ricevono eventi dall'host di Funzioni. Qualsiasi linguaggio che supporta primitive HTTP può implementare un gestore personalizzato.
I gestori personalizzati sono più adatti per le situazioni in cui si vuole:
- Implementare un'app per le funzioni in un linguaggio attualmente non usata, ad esempio Go o Rust.
- Implementare un'app per le funzioni in un runtime attualmente non disponibile per impostazione predefinita, ad esempio Deno.
Con i gestori personalizzati, è possibile usare trigger e associazioni di input e output tramite bundle di estensioni.
Introduzione a Funzioni di Azure gestori personalizzati con guide introduttive in Go e Rust.
Panoramica
Il diagramma seguente illustra la relazione tra l'host funzioni e un server Web implementato come gestore personalizzato.
- Ogni evento attiva una richiesta inviata all'host di Funzioni. Un evento è qualsiasi trigger supportato da Funzioni di Azure.
- L'host funzioni invia quindi un payload di richiesta al server Web. Il payload contiene i dati di trigger e di associazione di input e altri metadati per la funzione.
- Il server Web esegue la singola funzione e restituisce un payload di risposta all'host funzioni.
- L'host funzioni passa i dati dalla risposta alle associazioni di output della funzione per l'elaborazione.
Un'app Funzioni di Azure implementata come gestore personalizzato deve configurare i file host.json, local.settings.json e function.json in base a alcune convenzioni.
Struttura dell'applicazione
Per implementare un gestore personalizzato, sono necessari gli aspetti seguenti per l'applicazione:
- Un file host.json nella radice dell'app
- Un file local.settings.json nella radice dell'app
- Un file function.json per ogni funzione (all'interno di una cartella che corrisponde al nome della funzione)
- Comando, script o eseguibile, che esegue un server Web
Il diagramma seguente mostra l'aspetto di questi file nel file system per una funzione denominata "MyQueueFunction" e un eseguibile del gestore personalizzato denominato handler.exe.
| /MyQueueFunction
| function.json
|
| host.json
| local.settings.json
| handler.exe
Impostazione
L'applicazione viene configurata tramite i file host.json e local.settings.json .
host.json
host.json indica all'host di Funzioni dove inviare richieste puntando a un server Web in grado di elaborare gli eventi HTTP.
Un gestore personalizzato viene definito configurando il file host.json con informazioni dettagliate su come eseguire il server Web tramite la customHandler
sezione .
{
"version": "2.0",
"customHandler": {
"description": {
"defaultExecutablePath": "handler.exe"
}
}
}
La customHandler
sezione punta a una destinazione come definito da defaultExecutablePath
. La destinazione di esecuzione può essere un comando, un eseguibile o un file in cui viene implementato il server Web.
Usare la arguments
matrice per passare qualsiasi argomento all'eseguibile. Gli argomenti supportano l'espansione delle variabili di ambiente (impostazioni dell'applicazione) usando %%
la notazione.
È anche possibile modificare la directory di lavoro usata dall'eseguibile con workingDirectory
.
{
"version": "2.0",
"customHandler": {
"description": {
"defaultExecutablePath": "app/handler.exe",
"arguments": [
"--database-connection-string",
"%DATABASE_CONNECTION_STRING%"
],
"workingDirectory": "app"
}
}
}
Supporto delle associazioni
I trigger standard insieme alle associazioni di input e output sono disponibili facendo riferimento ai bundle di estensioni nel file host.json .
local.settings.json
local.settings.json definisce le impostazioni dell'applicazione usate durante l'esecuzione dell'app per le funzioni in locale. Poiché può contenere segreti, local.settings.json deve essere escluso dal controllo del codice sorgente. In Azure usare invece le impostazioni dell'applicazione.
Per i gestori personalizzati, impostare su FUNCTIONS_WORKER_RUNTIME
Custom
in local.settings.json.
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "Custom"
}
}
Metadati della funzione
Se usato con un gestore personalizzato, il contenuto function.json non è diverso da come definire una funzione in qualsiasi altro contesto. L'unico requisito è che i file function.json devono trovarsi in una cartella denominata in modo che corrisponda al nome della funzione.
L'function.json seguente configura una funzione con un trigger della coda e un'associazione di output della coda. Poiché si trova in una cartella denominata MyQueueFunction, definisce una funzione denominata MyQueueFunction.
MyQueueFunction/function.json
{
"bindings": [
{
"name": "myQueueItem",
"type": "queueTrigger",
"direction": "in",
"queueName": "messages-incoming",
"connection": "AzureWebJobsStorage"
},
{
"name": "$return",
"type": "queue",
"direction": "out",
"queueName": "messages-outgoing",
"connection": "AzureWebJobsStorage"
}
]
}
Payload della richiesta
Quando viene ricevuto un messaggio di coda, l'host di Funzioni invia una richiesta di post HTTP al gestore personalizzato con un payload nel corpo.
Il codice seguente rappresenta un payload di richiesta di esempio. Il payload include una struttura JSON con due membri: Data
e Metadata
.
Il Data
membro include chiavi che corrispondono ai nomi di input e trigger definiti nella matrice di associazioni nel file function.json .
Il Metadata
membro include i metadati generati dall'origine evento.
{
"Data": {
"myQueueItem": "{ message: \"Message sent\" }"
},
"Metadata": {
"DequeueCount": 1,
"ExpirationTime": "2019-10-16T17:58:31+00:00",
"Id": "800ae4b3-bdd2-4c08-badd-f08e5a34b865",
"InsertionTime": "2019-10-09T17:58:31+00:00",
"NextVisibleTime": "2019-10-09T18:08:32+00:00",
"PopReceipt": "AgAAAAMAAAAAAAAAAgtnj8x+1QE=",
"sys": {
"MethodName": "QueueTrigger",
"UtcNow": "2019-10-09T17:58:32.2205399Z",
"RandGuid": "24ad4c06-24ad-4e5b-8294-3da9714877e9"
}
}
}
Payload della risposta
Per convenzione, le risposte delle funzioni vengono formattate come coppie chiave/valore. Le chiavi supportate includono:
Tipo di dati | Osservazioni: | |
---|---|---|
Outputs |
oggetto | Contiene i valori di risposta definiti dalla bindings matrice in function.json.Ad esempio, se una funzione è configurata con un'associazione di output della coda denominata "myQueueOutput", Outputs contiene una chiave denominata myQueueOutput , impostata dal gestore personalizzato sui messaggi inviati alla coda. |
Logs |
array | I messaggi vengono visualizzati nei log chiamate di Funzioni. Durante l'esecuzione in Azure, i messaggi vengono visualizzati in Application Insights. |
ReturnValue |
string | Usato per fornire una risposta quando un output è configurato come $return nel file function.json . |
Questo è un esempio di payload della risposta.
{
"Outputs": {
"res": {
"body": "Message enqueued"
},
"myQueueOutput": [
"queue message 1",
"queue message 2"
]
},
"Logs": [
"Log message 1",
"Log message 2"
],
"ReturnValue": "{\"hello\":\"world\"}"
}
Esempi
I gestori personalizzati possono essere implementati in qualsiasi linguaggio che supporti la ricezione di eventi HTTP. Gli esempi seguenti illustrano come implementare un gestore personalizzato usando il linguaggio di programmazione Go.
Funzione con associazioni
Lo scenario implementato in questo esempio include una funzione denominata order
che accetta un POST
oggetto con un payload che rappresenta un ordine di prodotto. Quando viene inviato un ordine alla funzione, viene creato un messaggio di archiviazione code e viene restituita una risposta HTTP.
Implementazione
In una cartella denominata order, il file function.json configura la funzione attivata da HTTP.
order/function.json
{
"bindings": [
{
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "queue",
"name": "message",
"direction": "out",
"queueName": "orders",
"connection": "AzureWebJobsStorage"
}
]
}
Questa funzione è definita come funzione attivata da HTTP che restituisce una risposta HTTP e restituisce un messaggio di archiviazione code.
Nella radice dell'app, il file host.json è configurato per eseguire un file eseguibile denominato handler.exe
(handler
in Linux o macOS).
{
"version": "2.0",
"customHandler": {
"description": {
"defaultExecutablePath": "handler.exe"
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
Si tratta della richiesta HTTP inviata al runtime di Funzioni.
POST http://127.0.0.1:7071/api/order HTTP/1.1
Content-Type: application/json
{
"id": 1005,
"quantity": 2,
"color": "black"
}
Il runtime di Funzioni invierà quindi la richiesta HTTP seguente al gestore personalizzato:
POST http://127.0.0.1:<FUNCTIONS_CUSTOMHANDLER_PORT>/order HTTP/1.1
Content-Type: application/json
{
"Data": {
"req": {
"Url": "http://localhost:7071/api/order",
"Method": "POST",
"Query": "{}",
"Headers": {
"Content-Type": [
"application/json"
]
},
"Params": {},
"Body": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}"
}
},
"Metadata": {
}
}
Nota
Alcune parti del payload sono state rimosse per brevità.
handler.exe è il programma del gestore personalizzato Go compilato che esegue un server Web e risponde alle richieste di chiamata di funzione dall'host funzioni.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type InvokeRequest struct {
Data map[string]json.RawMessage
Metadata map[string]interface{}
}
type InvokeResponse struct {
Outputs map[string]interface{}
Logs []string
ReturnValue interface{}
}
func orderHandler(w http.ResponseWriter, r *http.Request) {
var invokeRequest InvokeRequest
d := json.NewDecoder(r.Body)
d.Decode(&invokeRequest)
var reqData map[string]interface{}
json.Unmarshal(invokeRequest.Data["req"], &reqData)
outputs := make(map[string]interface{})
outputs["message"] = reqData["Body"]
resData := make(map[string]interface{})
resData["body"] = "Order enqueued"
outputs["res"] = resData
invokeResponse := InvokeResponse{outputs, nil, nil}
responseJson, _ := json.Marshal(invokeResponse)
w.Header().Set("Content-Type", "application/json")
w.Write(responseJson)
}
func main() {
customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
if !exists {
customHandlerPort = "8080"
}
mux := http.NewServeMux()
mux.HandleFunc("/order", orderHandler)
fmt.Println("Go server Listening on: ", customHandlerPort)
log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}
In questo esempio, il gestore personalizzato esegue un server Web per gestire gli eventi HTTP ed è impostato per l'ascolto delle richieste tramite .FUNCTIONS_CUSTOMHANDLER_PORT
Anche se l'host funzioni ha ricevuto la richiesta HTTP originale in /api/order
, richiama il gestore personalizzato usando il nome della funzione (nome della cartella). In questo esempio la funzione viene definita nel percorso di /order
. L'host invia al gestore personalizzato una richiesta HTTP nel percorso di /order
.
Quando POST
le richieste vengono inviate a questa funzione, i dati del trigger e i metadati della funzione sono disponibili tramite il corpo della richiesta HTTP. È possibile accedere al corpo della richiesta HTTP originale nel payload.Data.req.Body
La risposta della funzione viene formattata in coppie chiave/valore in cui il Outputs
membro contiene un valore JSON in cui le chiavi corrispondono agli output come definito nel file function.json .
Si tratta di un payload di esempio restituito da questo gestore all'host di Funzioni.
{
"Outputs": {
"message": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}",
"res": {
"body": "Order enqueued"
}
},
"Logs": null,
"ReturnValue": null
}
Impostando l'output uguale ai dati dell'ordine message
provenienti dalla richiesta, la funzione restituisce i dati nell'ordine alla coda configurata. L'host funzioni restituisce anche la risposta HTTP configurata nel res
chiamante.
Funzione solo HTTP
Per le funzioni attivate da HTTP senza associazioni o output aggiuntivi, è possibile che il gestore funzioni direttamente con la richiesta e la risposta HTTP anziché con i payload di richiesta e risposta del gestore personalizzato. Questo comportamento può essere configurato in host.json usando l'impostazione enableForwardingHttpRequest
.
Importante
Lo scopo principale della funzionalità dei gestori personalizzati è abilitare linguaggi e runtime che attualmente non dispongono del supporto di prima classe in Funzioni di Azure. Sebbene sia possibile eseguire applicazioni Web usando gestori personalizzati, Funzioni di Azure non è un proxy inverso standard. Alcune funzionalità, ad esempio lo streaming delle risposte, HTTP/2 e WebSocket, non sono disponibili. Alcuni componenti della richiesta HTTP, ad esempio alcune intestazioni e route, possono essere limitati. L'applicazione potrebbe anche riscontrare un avvio a freddo eccessivo.
Per risolvere queste circostanze, prendere in considerazione l'esecuzione delle app Web nel servizio app Azure.
L'esempio seguente illustra come configurare una funzione attivata da HTTP senza associazioni o output aggiuntivi. Lo scenario implementato in questo esempio include una funzione denominata hello
che accetta un GET
oggetto o POST
.
Implementazione
In una cartella denominata hello il file function.json configura la funzione attivata da HTTP.
hello/function.json
{
"bindings": [
{
"type": "httpTrigger",
"authLevel": "anonymous",
"direction": "in",
"name": "req",
"methods": ["get", "post"]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
La funzione è configurata per accettare le GET
richieste e POST
e il valore del risultato viene fornito tramite un argomento denominato res
.
Nella radice dell'app, il file host.json è configurato per l'esecuzione handler.exe
ed enableForwardingHttpRequest
è impostato su true
.
{
"version": "2.0",
"customHandler": {
"description": {
"defaultExecutablePath": "handler.exe"
},
"enableForwardingHttpRequest": true
}
}
Quando enableForwardingHttpRequest
è true
, il comportamento delle funzioni solo HTTP è diverso dal comportamento predefinito dei gestori personalizzati nei modi seguenti:
- La richiesta HTTP non contiene il payload della richiesta dei gestori personalizzati. In realtà, l'host di Funzioni richiama il gestore con una copia della richiesta HTTP originale.
- L'host funzioni richiama il gestore con lo stesso percorso della richiesta originale, inclusi tutti i parametri della stringa di query.
- L'host di Funzioni restituisce una copia della risposta HTTP del gestore come risposta alla richiesta originale.
Di seguito è riportata una richiesta POST all'host funzioni. L'host funzioni invia quindi una copia della richiesta al gestore personalizzato nello stesso percorso.
POST http://127.0.0.1:7071/api/hello HTTP/1.1
Content-Type: application/json
{
"message": "Hello World!"
}
Il file handler.go implementa un server Web e una funzione HTTP.
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method == "GET" {
w.Write([]byte("hello world"))
} else {
body, _ := ioutil.ReadAll(r.Body)
w.Write(body)
}
}
func main() {
customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
if !exists {
customHandlerPort = "8080"
}
mux := http.NewServeMux()
mux.HandleFunc("/api/hello", helloHandler)
fmt.Println("Go server Listening on: ", customHandlerPort)
log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}
In questo esempio, il gestore personalizzato crea un server Web per gestire gli eventi HTTP ed è impostato per l'ascolto delle richieste tramite .FUNCTIONS_CUSTOMHANDLER_PORT
GET
le richieste vengono gestite restituendo una stringa e POST
le richieste hanno accesso al corpo della richiesta.
La route per la funzione order è /api/hello
, uguale alla richiesta originale.
Nota
non FUNCTIONS_CUSTOMHANDLER_PORT
è la porta pubblica usata per chiamare la funzione. Questa porta viene usata dall'host funzioni per chiamare il gestore personalizzato.
Distribuzione
Un gestore personalizzato può essere distribuito in ogni opzione di hosting Funzioni di Azure. Se il gestore richiede dipendenze del sistema operativo o della piattaforma ,ad esempio un runtime di linguaggio, potrebbe essere necessario usare un contenitore personalizzato.
Quando si crea un'app per le funzioni in Azure per gestori personalizzati, è consigliabile selezionare .NET Core come stack.
Per distribuire un'app del gestore personalizzata usando Funzioni di Azure Core Tools, eseguire il comando seguente.
func azure functionapp publish $functionAppName
Nota
Assicurarsi che tutti i file necessari per eseguire il gestore personalizzato si trovino nella cartella e inclusi nella distribuzione. Se il gestore personalizzato è un eseguibile binario o ha dipendenze specifiche della piattaforma, assicurarsi che questi file corrispondano alla piattaforma di distribuzione di destinazione.
Restrizioni
- Il server Web del gestore personalizzato deve essere avviato entro 60 secondi.
Esempi
Vedere il repository GitHub di esempi di gestori personalizzati per esempi di come implementare funzioni in un'ampia gamma di linguaggi.
Risoluzione dei problemi e supporto
Registrazione della traccia
Se l'avvio del processo del gestore personalizzato non riesce o se presenta problemi di comunicazione con l'host funzioni, è possibile aumentare il livello di log dell'app per le funzioni per Trace
visualizzare altri messaggi di diagnostica dall'host.
Per modificare il livello di log predefinito dell'app per le funzioni, configurare l'impostazione logLevel
nella logging
sezione di host.json.
{
"version": "2.0",
"customHandler": {
"description": {
"defaultExecutablePath": "handler.exe"
}
},
"logging": {
"logLevel": {
"default": "Trace"
}
}
}
L'host funzioni restituisce messaggi di log aggiuntivi, incluse le informazioni correlate al processo del gestore personalizzato. Usare i log per analizzare i problemi durante l'avvio del processo del gestore personalizzato o la chiamata di funzioni nel gestore personalizzato.
In locale, i log vengono stampati nella console.
In Azure eseguire query sulle tracce di Application Insights per visualizzare i messaggi di log. Se l'app produce un volume elevato di log, solo un subset di messaggi di log viene inviato ad Application Insights. Disabilitare il campionamento per assicurarsi che tutti i messaggi vengano registrati.
Testare il gestore personalizzato in isolamento
Le app del gestore personalizzato sono un processo del server Web, quindi può essere utile avviarlo autonomamente e testare le chiamate di funzione inviando richieste HTTP fittizie. Per l'invio di richieste HTTP con payload, assicurarsi di scegliere uno strumento che consente di proteggere i dati. Per altre informazioni, vedere Strumenti di test HTTP.
È anche possibile usare questa strategia nelle pipeline CI/CD per eseguire test automatizzati nel gestore personalizzato.
Ambiente di esecuzione
I gestori personalizzati vengono eseguiti nello stesso ambiente di un'app Funzioni di Azure tipica. Testare il gestore per assicurarsi che l'ambiente contenga tutte le dipendenze necessarie per l'esecuzione. Per le app che richiedono dipendenze aggiuntive, potrebbe essere necessario eseguirle usando un'immagine del contenitore personalizzata ospitata in Funzioni di Azure piano Premium.
Ottenere supporto
Se è necessaria assistenza su un'app per le funzioni con gestori personalizzati, è possibile inviare una richiesta tramite canali di supporto normali. Tuttavia, a causa dell'ampia gamma di linguaggi possibili usati per creare app di gestori personalizzati, il supporto non è illimitato.
Il supporto è disponibile se l'host di Funzioni presenta problemi durante l'avvio o la comunicazione con il processo del gestore personalizzato. Per problemi specifici dei lavori interni del processo del gestore personalizzato, ad esempio problemi relativi al linguaggio o al framework scelto, il team di supporto non è in grado di fornire assistenza in questo contesto.
Passaggi successivi
Introduzione alla creazione di un'app Funzioni di Azure in Go o Rust con l'avvio rapido dei gestori personalizzati.