Gestori personalizzati di Funzioni di Azure

Ogni app Funzioni viene eseguita da un gestore specifico del linguaggio. Mentre Funzioni di Azure funzionalità 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 funzioni. Qualsiasi linguaggio che supporta le primitive HTTP può implementare un gestore personalizzato.

I gestori personalizzati sono più adatti alle situazioni in cui si vuole:

  • Implementare un'app per le funzioni in una lingua attualmente non disponibile, ad esempio Go o Rust.
  • Implementare un'app per le funzioni in un runtime attualmente non presente per impostazione predefinita, ad esempio Deno.

Con gestori personalizzati, è possibile usare trigger e associazioni di input e output tramite bundle di estensione.

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.

panoramica Funzioni di Azure gestore personalizzato

  1. Ogni evento attiva una richiesta inviata all'host Funzioni. Un evento è qualsiasi trigger supportato da Funzioni di Azure.
  2. L'host Funzioni invia quindi un payload di richiesta al server Web. Il payload contiene dati di trigger e associazione di input e altri metadati per la funzione.
  3. Il server Web esegue la singola funzione e restituisce un payload di risposta all'host Funzioni.
  4. 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:

  • File host.json nella radice dell'app
  • File local.settings.json nella radice dell'app
  • File function.json per ogni funzione (all'interno di una cartella corrispondente al nome della funzione)
  • Comando, script o eseguibile, che esegue un server Web

Il diagramma seguente illustra come questi file cercano nel file system una funzione denominata "MyQueueFunction" e un eseguibile del gestore personalizzato denominato handler.exe.

| /MyQueueFunction
|   function.json
|
| host.json
| local.settings.json
| handler.exe

Configurazione

L'applicazione viene configurata tramite i file host.json e local.settings.json .

host.json

host.json indica all'host Funzioni dove inviare richieste puntando a un server Web in grado di elaborare 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 definita da defaultExecutablePath. La destinazione di esecuzione può essere un comando, un file eseguibile o un file in cui viene implementato il server Web.

Usare la matrice per passare gli argomenti all'eseguibile arguments . 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 FUNCTIONS_WORKER_RUNTIME su Custom in local.settings.json.

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "Custom"
  }
}

Metadati delle funzioni

Quando viene 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 per corrispondere al nome della funzione.

La funzione.json seguente configura una funzione con un trigger di 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 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 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:

Chiave payload Tipo di dati Commenti
Outputs object 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", contiene Outputs una chiave denominata myQueueOutput, impostata dal gestore personalizzato sui messaggi inviati alla coda.
Logs array I messaggi vengono visualizzati nei log di chiamata di Funzioni.

Quando viene eseguito 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 di risposta.

{
  "Outputs": {
    "res": {
      "body": "Message enqueued"
    },
    "myQueueOutput": [
      "queue message 1",
      "queue message 2"
    ]
  },
  "Logs": [
    "Log message 1",
    "Log message 2"
  ],
  "ReturnValue": "{\"hello\":\"world\"}"
}

Esempio

I gestori personalizzati possono essere implementati in qualsiasi linguaggio che supporta 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 ordine, 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 viene 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 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 ascoltare le 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 delle funzioni sono disponibili tramite il corpo della richiesta HTTP. Il corpo della richiesta HTTP originale può essere accessibile 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 .

Questo è un payload di esempio che questo gestore restituisce all'host Funzioni.

{
  "Outputs": {
    "message": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}",
    "res": {
      "body": "Order enqueued"
    }
  },
  "Logs": null,
  "ReturnValue": null
}

Impostando l'output message uguale ai dati dell'ordine che provengono dalla richiesta, la funzione restituisce i dati per la 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 HTTP e la risposta anziché i payload di richiesta e risposta personalizzati. Questo comportamento può essere configurato in host.json usando l'impostazione enableForwardingHttpRequest .

Importante

Lo scopo principale della funzionalità gestori personalizzati consiste nell'abilitare linguaggi e runtime che attualmente non dispongono del supporto di prima classe in Funzioni di Azure. Anche se potrebbe essere possibile eseguire applicazioni Web usando gestori personalizzati, Funzioni di Azure non è un proxy inverso standard. Alcune funzionalità, ad esempio streaming di risposte, HTTP/2 e WebSockets non sono disponibili. Alcuni componenti della richiesta HTTP, ad esempio alcune intestazioni e route, possono essere limitati. L'applicazione può anche riscontrare un avvio ad accesso sporadico eccessivo.

Per risolvere questi casi, prendere in considerazione l'esecuzione delle app Web in Servizio app di Azure.

Nell'esempio seguente viene illustrato 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 sia GETPOST richieste che richieste 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 e enableForwardingHttpRequest viene impostato su true.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    },
    "enableForwardingHttpRequest": true
  }
}

Quando enableForwardingHttpRequest è true, il comportamento delle funzioni solo HTTP differisce dal comportamento dei gestori personalizzati predefiniti in questi modi:

  • 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 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 ascoltare le 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 di ordine è /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 gestore personalizzata usando Funzioni di Azure Strumenti di base, 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 iniziare entro 60 secondi.

Esempi

Per esempi di come implementare funzioni in diversi linguaggi, vedere il repository GitHub personalizzato .

Risoluzione dei problemi e supporto

Registrazione di traccia

Se il processo del gestore personalizzato non riesce a avviare o se ha 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 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 che avviano il processo del gestore personalizzato o richiamano le 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, viene inviato solo un subset di messaggi di log a 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 in una chiamata di funzione personalizzata e di test inviando richieste HTTP fittizie usando uno strumento come cURL o Postman.

È 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 tipica Funzioni di Azure. 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.

Supporto

Se è necessaria assistenza su un'app per le funzioni con gestori personalizzati, è possibile inviare una richiesta tramite canali di supporto regolari. Tuttavia, a causa dell'ampia gamma di lingue possibili usate per creare app di gestori personalizzati, il supporto non è illimitato.

Il supporto è disponibile se l'host funzioni ha problemi di avvio o comunicazione con il processo del gestore personalizzato. Per problemi specifici dei lavori interni del processo di 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 la guida introduttiva dei gestori personalizzati.