Aangepaste handlers voor Azure Functions

Elke Functions-app wordt uitgevoerd door een taalspecifieke handler. Hoewel Azure Functions standaard veel taalhandlers bevat, zijn er gevallen waarin u mogelijk andere talen of runtimes wilt gebruiken.

Aangepaste handlers zijn lichtgewicht webservers die gebeurtenissen ontvangen van de Functions-host. Elke taal die HTTP-primitieven ondersteunt, kan een aangepaste handler implementeren.

Aangepaste handlers zijn het meest geschikt voor situaties waarin u het volgende wilt doen:

  • Implementeer een functie-app in een taal die momenteel niet standaard wordt aangeboden, zoals Go of Rust.
  • Implementeer een functie-app in een runtime die momenteel niet standaard wordt aanbevolen, zoals Deno.

Met aangepaste handlers kunt u triggers en invoer- en uitvoerbindingen gebruiken via extensiebundels.

Ga aan de slag met Azure Functions aangepaste handlers met quickstarts in Go en Rust.

Overzicht

In het volgende diagram ziet u de relatie tussen de Functions-host en een webserver die is geïmplementeerd als een aangepaste handler.

Overzicht van aangepaste handler Azure Functions

  1. Elke gebeurtenis activeert een aanvraag die naar de Functions-host wordt verzonden. Een gebeurtenis is een trigger die wordt ondersteund door Azure Functions.
  2. De Functions-host geeft vervolgens een nettolading van de aanvraag uit aan de webserver. De nettolading bevat trigger- en invoerbindingsgegevens en andere metagegevens voor de functie.
  3. De webserver voert de afzonderlijke functie uit en retourneert een nettolading van het antwoord naar de Functions-host.
  4. De Functions-host geeft gegevens van het antwoord door aan de uitvoerbindingen van de functie voor verwerking.

Een Azure Functions-app die is geïmplementeerd als een aangepaste handler, moet de bestanden host.json, local.settings.json en function.json volgens enkele conventies configureren.

Toepassingsstructuur

Als u een aangepaste handler wilt implementeren, hebt u de volgende aspecten voor uw toepassing nodig:

  • Een host.json-bestand in de hoofdmap van uw app
  • Een local.settings.json-bestand in de hoofdmap van uw app
  • Een function.json-bestand voor elke functie (in een map die overeenkomt met de functienaam)
  • Een opdracht, script of uitvoerbaar bestand waarmee een webserver wordt uitgevoerd

In het volgende diagram ziet u hoe deze bestanden er in het bestandssysteem uitzien voor een functie met de naam 'MyQueueFunction' en een aangepast uitvoerbare handler met de naamhandler.exe.

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

Configuratie

De toepassing wordt geconfigureerd via de bestanden host.json en local.settings.json .

host.json

host.json vertelt de Functions-host waar aanvragen moeten worden verzonden door te verwijzen naar een webserver die HTTP-gebeurtenissen kan verwerken.

Een aangepaste handler wordt gedefinieerd door het host.json-bestand te configureren met details over het uitvoeren van de webserver via de customHandler sectie.

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

De customHandler sectie verwijst naar een doel zoals gedefinieerd door de defaultExecutablePath. Het uitvoeringsdoel kan een opdracht, uitvoerbaar bestand of bestand zijn waarin de webserver is geïmplementeerd.

Gebruik de arguments matrix om argumenten door te geven aan het uitvoerbare bestand. Argumenten ondersteunen uitbreiding van omgevingsvariabelen (toepassingsinstellingen) met behulp van %% notatie.

U kunt ook de werkmap die door het uitvoerbare bestand wordt gebruikt, wijzigen met workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Ondersteuning voor bindingen

Standaardtriggers samen met invoer- en uitvoerbindingen zijn beschikbaar door te verwijzen naar extensiebundels in uw host.json-bestand .

local.settings.json

local.settings.json definieert toepassingsinstellingen die worden gebruikt bij het lokaal uitvoeren van de functie-app. Omdat het geheimen kan bevatten, moet local.settings.json worden uitgesloten van broncodebeheer. Gebruik in Azure in plaats daarvan toepassingsinstellingen.

Stel voor aangepaste handlers in FUNCTIONS_WORKER_RUNTIME op Custom in local.settings.json.

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

Functiemetagegevens

Bij gebruik met een aangepaste handler verschilt de inhoud van function.json niet van de manier waarop u een functie in een andere context definieert. De enige vereiste is dat function.json-bestanden zich in een map met de naam moeten bevinden die overeenkomt met de functienaam.

Met de volgende function.json configureert u een functie met een wachtrijtrigger en een wachtrijuitvoerbinding. Omdat deze zich in een map met de naam MyQueueFunction bevindt, wordt een functie met de naam MyQueueFunction gedefinieerd.

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"
    }
  ]
}

Nettolading aanvragen

Wanneer een wachtrijbericht wordt ontvangen, verzendt de Functions-host een HTTP-aanvraag na de aanvraag naar de aangepaste handler met een nettolading in de hoofdtekst.

De volgende code vertegenwoordigt een voorbeeld van de nettolading van een aanvraag. De nettolading bevat een JSON-structuur met twee leden: Data en Metadata.

Het Data lid bevat sleutels die overeenkomen met invoer- en triggernamen zoals gedefinieerd in de bindingsmatrix in het bestand function.json .

Het Metadata lid bevat metagegevens die zijn gegenereerd op basis van de gebeurtenisbron.

{
  "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"
    }
  }
}

Nettolading van antwoord

Functiereacties worden volgens de conventie opgemaakt als sleutel-waardeparen. Ondersteunde sleutels zijn onder andere:

Nettoladingssleutel Gegevenstype Opmerkingen
Outputs object Bevat antwoordwaarden zoals gedefinieerd door de bindings matrix in function.json.

Als een functie bijvoorbeeld is geconfigureerd met een wachtrijuitvoerbinding met de naam 'myQueueOutput', bevat deze Outputs een sleutel met de naam myQueueOutput, die door de aangepaste handler wordt ingesteld op de berichten die naar de wachtrij worden verzonden.
Logs matrix Berichten worden weergegeven in de aanroeplogboeken van Functions.

Wanneer u in Azure wordt uitgevoerd, worden berichten weergegeven in Application Insights.
ReturnValue tekenreeks Wordt gebruikt om een antwoord te geven wanneer een uitvoer is geconfigureerd zoals $return in het bestand function.json .

Dit is een voorbeeld van een nettolading van een antwoord.

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

Voorbeelden

Aangepaste handlers kunnen worden geïmplementeerd in elke taal die ondersteuning biedt voor het ontvangen van HTTP-gebeurtenissen. In de volgende voorbeelden ziet u hoe u een aangepaste handler implementeert met behulp van de programmeertaal Go.

Functie met bindingen

Het scenario dat in dit voorbeeld is geïmplementeerd, bevat een functie met de naam order die een POST accepteert met een nettolading die een productorder vertegenwoordigt. Wanneer een order naar de functie wordt geplaatst, wordt er een Queue Storage-bericht gemaakt en wordt een HTTP-antwoord geretourneerd.

Implementatie

In een map met de naam volgorde configureert het bestand function.json de door HTTP geactiveerde functie.

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"
    }
  ]
}

Deze functie is gedefinieerd als een door HTTP geactiveerde functie die een HTTP-antwoord retourneert en een Queue Storage-bericht uitvoert.

In de hoofdmap van de app is het bestand host.json geconfigureerd voor het uitvoeren van een uitvoerbaar bestand met de naam handler.exe (handler in Linux of macOS).

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[1.*, 2.0.0)"
  }
}

Dit is de HTTP-aanvraag die is verzonden naar de Functions-runtime.

POST http://127.0.0.1:7071/api/order HTTP/1.1
Content-Type: application/json

{
  "id": 1005,
  "quantity": 2,
  "color": "black"
}

De Functions-runtime verzendt vervolgens de volgende HTTP-aanvraag naar de aangepaste handler:

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": {
  }
}

Notitie

Sommige gedeelten van de nettolading zijn voor het kort verwijderd.

handler.exe is het gecompileerde aangepaste Go-handlerprogramma dat een webserver uitvoert en reageert op functie-aanroepaanvragen van de Functions-host.

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 dit voorbeeld voert de aangepaste handler een webserver uit om HTTP-gebeurtenissen af te handelen en wordt deze ingesteld op het luisteren naar aanvragen via de FUNCTIONS_CUSTOMHANDLER_PORT.

Hoewel de Functions-host de oorspronkelijke HTTP-aanvraag heeft ontvangen op /api/order, wordt de aangepaste handler aangeroepen met behulp van de functienaam (de mapnaam). In dit voorbeeld wordt de functie gedefinieerd op het pad van /order. De host verzendt de aangepaste handler een HTTP-aanvraag op het pad van /order.

Wanneer POST aanvragen naar deze functie worden verzonden, zijn de triggergegevens en functiemetagegevens beschikbaar via de HTTP-aanvraagbody. De oorspronkelijke HTTP-aanvraagbody kan worden geopend in de payload.Data.req.Body

Het antwoord van de functie is opgemaakt in sleutel-/waardeparen waarbij het Outputs lid een JSON-waarde bevat waarbij de sleutels overeenkomen met de uitvoer zoals gedefinieerd in het bestand function.json .

Dit is een voorbeeld van een nettolading die deze handler retourneert naar de Functions-host.

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

Door de message uitvoer gelijk te stellen aan de ordergegevens die afkomstig zijn van de aanvraag, voert de functie de ordergegevens uit naar de geconfigureerde wachtrij. De Functions-host retourneert ook het HTTP-antwoord dat is geconfigureerd in res de aanroeper.

Functie alleen HTTP

Voor door HTTP geactiveerde functies zonder extra bindingen of uitvoer wilt u mogelijk dat uw handler rechtstreeks met de HTTP-aanvraag en -reactie werkt in plaats van met de aangepaste handleraanvraag en antwoordpayloads . Dit gedrag kan worden geconfigureerd in host.json met behulp van de enableForwardingHttpRequest instelling.

Belangrijk

Het primaire doel van de functie voor aangepaste handlers is het inschakelen van talen en runtimes die momenteel geen eersteklas ondersteuning hebben voor Azure Functions. Hoewel het mogelijk is om webtoepassingen uit te voeren met behulp van aangepaste handlers, is Azure Functions geen standaard omgekeerde proxy. Sommige functies, zoals antwoordstreaming, HTTP/2 en WebSockets, zijn niet beschikbaar. Sommige onderdelen van de HTTP-aanvraag, zoals bepaalde headers en routes, kunnen worden beperkt. Uw toepassing kan ook te maken hebben met overmatige koude start.

Als u deze omstandigheden wilt aanpakken, kunt u overwegen om uw web-apps uit te voeren op Azure App Service.

In het volgende voorbeeld ziet u hoe u een door HTTP geactiveerde functie configureert zonder extra bindingen of uitvoer. Het scenario dat in dit voorbeeld is geïmplementeerd, bevat een functie met de naam hello die een GET of POST accepteert.

Implementatie

In een map met de naam hello configureert het bestand function.json de door HTTP geactiveerde functie.

hello/function.json

{
  "bindings": [
    {
      "type": "httpTrigger",
      "authLevel": "anonymous",
      "direction": "in",
      "name": "req",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

De functie is geconfigureerd om zowel als GETPOST aanvragen te accepteren en de resultaatwaarde wordt opgegeven via een argument met de naam res.

In de hoofdmap van de app is het bestand host.json geconfigureerd om te worden uitgevoerd handler.exe en enableForwardingHttpRequest ingesteld op true.

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

Wanneer enableForwardingHttpRequest is true, verschilt het gedrag van alleen HTTP-functies op de volgende manieren van het standaardgedrag van aangepaste handlers:

  • De HTTP-aanvraag bevat niet de nettolading van de aanvraag voor aangepaste handlers. In plaats daarvan roept de Functions-host de handler aan met een kopie van de oorspronkelijke HTTP-aanvraag.
  • De Functions-host roept de handler aan met hetzelfde pad als de oorspronkelijke aanvraag, inclusief queryreeksparameters.
  • De Functions-host retourneert een kopie van het HTTP-antwoord van de handler als antwoord op de oorspronkelijke aanvraag.

Hier volgt een POST-aanvraag voor de Functions-host. De Functions-host verzendt vervolgens een kopie van de aanvraag naar de aangepaste handler op hetzelfde pad.

POST http://127.0.0.1:7071/api/hello HTTP/1.1
Content-Type: application/json

{
  "message": "Hello World!"
}

Het bestand handler.go implementeert een webserver en HTTP-functie.

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 dit voorbeeld maakt de aangepaste handler een webserver om HTTP-gebeurtenissen af te handelen en is deze ingesteld om te luisteren naar aanvragen via de FUNCTIONS_CUSTOMHANDLER_PORT.

GET aanvragen worden verwerkt door een tekenreeks te retourneren en POST aanvragen hebben toegang tot de hoofdtekst van de aanvraag.

De route voor de orderfunctie hier is /api/hello, hetzelfde als de oorspronkelijke aanvraag.

Notitie

De FUNCTIONS_CUSTOMHANDLER_PORT is niet de openbare poort die wordt gebruikt om de functie aan te roepen. Deze poort wordt gebruikt door de Functions-host om de aangepaste handler aan te roepen.

Implementeren

Een aangepaste handler kan worden geïmplementeerd op elke Azure Functions hostingoptie. Als uw handler afhankelijkheden van het besturingssysteem of platform vereist (zoals een taalruntime), moet u mogelijk een aangepaste container gebruiken.

Wanneer u een functie-app maakt in Azure voor aangepaste handlers, wordt u aangeraden .NET Core als de stack te selecteren.

Voer de volgende opdracht uit om een aangepaste handler-app te implementeren met Azure Functions Core Tools.

func azure functionapp publish $functionAppName

Notitie

Zorg ervoor dat alle bestanden die nodig zijn om uw aangepaste handler uit te voeren, zich in de map bevinden en zijn opgenomen in de implementatie. Als uw aangepaste handler een binair uitvoerbaar bestand is of platformspecifieke afhankelijkheden heeft, moet u ervoor zorgen dat deze bestanden overeenkomen met het doelimplementatieplatform.

Beperkingen

  • De webserver van de aangepaste handler moet binnen 60 seconden worden gestart.

Voorbeelden

Raadpleeg de GitHub-opslagplaats met aangepaste handlervoorbeelden voor voorbeelden van het implementeren van functies in verschillende talen.

Probleemoplossing en ondersteuning

Traceringslogboekregistratie

Als het aangepaste handlerproces niet kan worden opgestart of als er problemen zijn met de communicatie met de Functions-host, kunt u het logboekniveau van de functie-app verhogen om Trace meer diagnostische berichten van de host te zien.

Als u het standaardlogboekniveau van de functie-app wilt wijzigen, configureert u de logLevel instelling in de logging sectie van host.json.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "logging": {
    "logLevel": {
      "default": "Trace"
    }
  }
}

De Functions-host voert extra logboekberichten uit, inclusief informatie met betrekking tot het aangepaste handlerproces. Gebruik de logboeken om problemen te onderzoeken bij het starten van het aangepaste handlerproces of het aanroepen van functies in uw aangepaste handler.

Lokaal worden logboeken naar de console afgedrukt.

Voer in Azure een query uit op Application Insights-traceringen om de logboekberichten weer te geven. Als uw app een groot aantal logboeken produceert, wordt slechts een subset van logboekberichten verzonden naar Application Insights. Schakel steekproeven uit om ervoor te zorgen dat alle berichten worden geregistreerd.

Aangepaste handler in isolatie testen

Aangepaste handler-apps zijn een webserverproces, dus het kan handig zijn om het zelf te starten en functie-aanroepen te testen door mock-HTTP-aanvragen te verzenden met behulp van een hulpprogramma zoals cURL of Postman.

U kunt deze strategie ook gebruiken in uw CI/CD-pijplijnen om geautomatiseerde tests uit te voeren op uw aangepaste handler.

Uitvoeringsomgeving

Aangepaste handlers worden uitgevoerd in dezelfde omgeving als een typische Azure Functions-app. Test uw handler om te controleren of de omgeving alle afhankelijkheden bevat die nodig zijn om uit te voeren. Voor apps waarvoor aanvullende afhankelijkheden zijn vereist, moet u deze mogelijk uitvoeren met behulp van een aangepaste containerinstallatiekopie die wordt gehost op Azure Functions Premium-abonnement.

Ondersteuning krijgen

Als u hulp nodig hebt bij een functie-app met aangepaste handlers, kunt u een aanvraag indienen via de reguliere ondersteuningskanalen. Vanwege de grote verscheidenheid aan mogelijke talen die worden gebruikt voor het bouwen van aangepaste handlers-apps, is de ondersteuning echter niet onbeperkt.

Ondersteuning is beschikbaar als de Functions-host problemen heeft met het starten of communiceren met het aangepaste handlerproces. Voor problemen die specifiek zijn voor de interne werking van uw aangepaste handlerproces, zoals problemen met de gekozen taal of framework, kan ons ondersteuningsteam in deze context geen hulp bieden.

Volgende stappen

Ga aan de slag met het bouwen van een Azure Functions-app in Go of Rust met de quickstart voor aangepaste handlers.