Anpassade hanterare för Azure Functions

Varje Functions-app körs av en språkspecifik hanterare. Azure Functions har många språkhanterare som standard, men det finns fall där du kanske vill använda andra språk eller körningsmiljöer.

Anpassade hanterare är lätta webbservrar som tar emot händelser från Functions-värden. Alla språk som stöder HTTP-primitiver kan implementera en anpassad hanterare.

Anpassade hanterare passar bäst för situationer där du vill:

  • Implementera en funktionsapp på ett språk som för närvarande inte erbjuds direkt, till exempel Go eller Rust.
  • Implementera en funktionsapp i en körning som för närvarande inte visas som standard, till exempel Deno.

Med anpassade hanterare kan du använda utlösare och indata- och utdatabindningar via tilläggspaket.

Kom igång med anpassade Azure Functions-hanterare med snabbstarter i Go och Rust.

Översikt

Följande diagram visar relationen mellan Functions-värden och en webbserver som implementerats som en anpassad hanterare.

Översikt över anpassad hanterare för Azure Functions

  1. Varje händelse utlöser en begäran som skickas till Functions-värden. En händelse är en utlösare som stöds av Azure Functions.
  2. Functions-värden utfärdar sedan en nyttolast för begäran till webbservern. Nyttolasten innehåller utlösar- och indatabindningsdata och andra metadata för funktionen.
  3. Webbservern kör den enskilda funktionen och returnerar en svarsnyttolast till Functions-värden.
  4. Functions-värden skickar data från svaret till funktionens utdatabindningar för bearbetning.

En Azure Functions-app som implementeras som en anpassad hanterare måste konfigurera host.json, local.settings.json och function.json filer enligt några konventioner.

Programstruktur

För att implementera en anpassad hanterare behöver du följande aspekter av ditt program:

  • En host.json fil i appens rot
  • En local.settings.json fil i appens rot
  • En function.json fil för varje funktion (i en mapp som matchar funktionsnamnet)
  • Ett kommando, ett skript eller en körbar fil som kör en webbserver

Följande diagram visar hur dessa filer ser ut i filsystemet för en funktion med namnet "MyQueueFunction" och en anpassad körbar hanterare med namnet handler.exe.

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

Konfiguration

Programmet konfigureras via filerna host.json och local.settings.json .

host.json

host.json talar om för Functions-värden var begäranden ska skickas genom att peka på en webbserver som kan bearbeta HTTP-händelser.

En anpassad hanterare definieras genom att konfigurera host.json-filen med information om hur du kör webbservern via customHandler avsnittet .

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

Avsnittet customHandler pekar på ett mål som definieras av defaultExecutablePath. Körningsmålet kan antingen vara ett kommando, en körbar fil eller en fil där webbservern implementeras.

Använd matrisen arguments för att skicka alla argument till den körbara filen. Argument stöder expansion av miljövariabler (programinställningar) med notation %% .

Du kan också ändra arbetskatalogen som används av den körbara filen med workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Stöd för bindningar

Standardutlösare tillsammans med indata- och utdatabindningar är tillgängliga genom att referera till tilläggspaket i din host.json-fil .

local.settings.json

local.settings.json definierar programinställningar som används när du kör funktionsappen lokalt. Eftersom den kan innehålla hemligheter bör local.settings.json undantas från källkontrollen. I Azure använder du programinställningar i stället.

För anpassade hanterare anger du FUNCTIONS_WORKER_RUNTIME till Custom i local.settings.json.

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

Funktionsmetadata

När det används med en anpassad hanterare skiljer sig function.json innehåll inte från hur du skulle definiera en funktion i någon annan kontext. Det enda kravet är att function.json filer måste finnas i en mapp med namnet för att matcha funktionsnamnet.

Följande function.json konfigurerar en funktion som har en köutlösare och en köutdatabindning. Eftersom den finns i en mapp med namnet MyQueueFunction, definierar den en funktion med namnet 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"
    }
  ]
}

Begär nyttolast

När ett kömeddelande tas emot skickar Functions-värden en HTTP-postbegäran till den anpassade hanteraren med en nyttolast i brödtexten.

Följande kod representerar en nyttolast för exempelbegäran. Nyttolasten innehåller en JSON-struktur med två medlemmar: Data och Metadata.

Medlemmen Data innehåller nycklar som matchar indata- och utlösarnamn enligt definitionen i bindningsmatrisen i function.json-filen.

Medlemmen Metadata innehåller metadata som genererats från händelsekällan.

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

Svarsnyttolast

Enligt konvention formateras funktionssvaren som nyckel/värde-par. Nycklar som stöds är:

Nyttolastnyckel Datatyp Kommentarer
Outputs objekt Innehåller svarsvärden som definierats av matrisen bindings i function.json.

Om en funktion till exempel har konfigurerats med en köutdatabindning med namnet "myQueueOutput" innehåller den Outputs en nyckel med namnet myQueueOutput, som anges av den anpassade hanteraren till de meddelanden som skickas till kön.
Logs matris Meddelanden visas i functions-anropsloggarna.

När du kör i Azure visas meddelanden i Application Insights.
ReturnValue sträng Används för att ge ett svar när utdata konfigureras som $return i function.json-filen.

Det här är ett exempel på en svarsnyttolast.

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

Exempel

Anpassade hanterare kan implementeras på valfritt språk som stöder mottagning av HTTP-händelser. I följande exempel visas hur du implementerar en anpassad hanterare med programmeringsspråket Go.

Funktion med bindningar

Scenariot som implementeras i det här exemplet har en funktion med namnet order som accepterar en POST med en nyttolast som representerar en produktbeställning. När en order skickas till funktionen skapas ett kölagringsmeddelande och ett HTTP-svar returneras.

Implementering

I en mapp med namnet order konfigurerar function.json-filen den HTTP-utlösta funktionen.

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

Den här funktionen definieras som en HTTP-utlöst funktion som returnerar ett HTTP-svar och matar ut ett kölagringsmeddelande .

I appens rot är host.json-filen konfigurerad för att köra en körbar fil med namnet handler.exe (handler i Linux eller macOS).

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

Det här är HTTP-begäran som skickas till Functions-körningen.

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

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

Functions-körningen skickar sedan följande HTTP-begäran till den anpassade hanteraren:

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

Kommentar

Vissa delar av nyttolasten har tagits bort för korthet.

handler.exe är det kompilerade go-anpassade hanteringsprogrammet som kör en webbserver och svarar på funktionsanropsbegäranden från Functions-värden.

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))
}

I det här exemplet kör den anpassade hanteraren en webbserver för att hantera HTTP-händelser och är inställd på att lyssna efter begäranden via FUNCTIONS_CUSTOMHANDLER_PORT.

Även om Functions-värden tog emot den ursprungliga HTTP-begäran på /api/orderanropas den anpassade hanteraren med hjälp av funktionsnamnet (dess mappnamn). I det här exemplet definieras funktionen i sökvägen /ordertill . Värden skickar en HTTP-begäran till den anpassade hanteraren på sökvägen /ordertill .

När POST begäranden skickas till den här funktionen är utlösardata och funktionsmetadata tillgängliga via HTTP-begärandetexten. Den ursprungliga HTTP-begärandetexten kan nås i nyttolastens Data.req.Body.

Funktionens svar formateras till nyckel/värde-par där Outputs medlemmen har ett JSON-värde där nycklarna matchar utdata enligt definitionen i filen function.json .

Det här är ett exempel på nyttolast som den här hanteraren returnerar till Functions-värden.

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

Genom att ange message utdata som är lika med de orderdata som kom in från begäran, matar funktionen ut data som beställer data till den konfigurerade kön. Functions-värden returnerar också DET HTTP-svar som konfigurerats i res till anroparen.

Funktionen ENDAST HTTP

För HTTP-utlösta funktioner utan ytterligare bindningar eller utdata kanske du vill att hanteraren ska arbeta direkt med HTTP-begäran och -svaret i stället för nyttolasten för anpassad hanteringsbegäran och svar. Det här beteendet kan konfigureras i host.json med hjälp av inställningen enableForwardingHttpRequest .

Viktigt!

Det primära syftet med funktionen för anpassade hanterare är att aktivera språk och körningar som för närvarande inte har förstklassigt stöd för Azure Functions. Även om det kan vara möjligt att köra webbprogram med anpassade hanterare är Azure Functions inte en omvänd standardproxy. Vissa funktioner som svarsströmning, HTTP/2 och WebSockets är inte tillgängliga. Vissa komponenter i HTTP-begäran, till exempel vissa huvuden och vägar, kan vara begränsade. Ditt program kan också uppleva överdriven kallstart.

Överväg att köra dina webbappar på Azure App Service för att hantera dessa omständigheter.

I följande exempel visas hur du konfigurerar en HTTP-utlöst funktion utan ytterligare bindningar eller utdata. Scenariot som implementeras i det här exemplet har en funktion med namnet hello som accepterar en GET eller POST .

Implementering

I en mapp med namnet hello konfigurerar function.json-filen den HTTP-utlösta funktionen.

hello/function.json

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

Funktionen är konfigurerad för att acceptera både GET och POST begäranden och resultatvärdet anges via ett argument med namnet res.

I appens rot är host.json-filen konfigurerad att köras handler.exe och enableForwardingHttpRequest är inställd på true.

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

När enableForwardingHttpRequest är trueskiljer sig beteendet för endast HTTP-funktioner från standardbeteendet för anpassade hanterare på följande sätt:

  • HTTP-begäran innehåller inte nyttolasten för anpassade hanterarbegäran. I stället anropar Functions-värden hanteraren med en kopia av den ursprungliga HTTP-begäran.
  • Functions-värden anropar hanteraren med samma sökväg som den ursprungliga begäran, inklusive eventuella frågesträngsparametrar.
  • Functions-värden returnerar en kopia av hanterarens HTTP-svar som svar på den ursprungliga begäran.

Följande är en POST-begäran till Functions-värden. Functions-värden skickar sedan en kopia av begäran till den anpassade hanteraren på samma sökväg.

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

{
  "message": "Hello World!"
}

Filen handler.go implementerar en webbserver och HTTP-funktion.

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))
}

I det här exemplet skapar den anpassade hanteraren en webbserver för att hantera HTTP-händelser och är inställd på att lyssna efter begäranden via FUNCTIONS_CUSTOMHANDLER_PORT.

GET begäranden hanteras genom att returnera en sträng och POST begäranden har åtkomst till begärandetexten.

Vägen för orderfunktionen här är /api/hello, samma som den ursprungliga begäran.

Kommentar

FUNCTIONS_CUSTOMHANDLER_PORT är inte den offentliga port som används för att anropa funktionen. Den här porten används av Functions-värden för att anropa den anpassade hanteraren.

Distribuera

En anpassad hanterare kan distribueras till varje Azure Functions-värdalternativ. Om hanteraren kräver operativsystem- eller plattformsberoenden (till exempel en språkkörning) kan du behöva använda en anpassad container.

När du skapar en funktionsapp i Azure för anpassade hanterare rekommenderar vi att du väljer .NET Core som stack.

Kör följande kommando för att distribuera en anpassad hanteringsapp med Hjälp av Azure Functions Core Tools.

func azure functionapp publish $functionAppName

Kommentar

Kontrollera att alla filer som krävs för att köra din anpassade hanterare finns i mappen och ingår i distributionen. Om din anpassade hanterare är binär körbar eller har plattformsspecifika beroenden kontrollerar du att filerna matchar måldistributionsplattformen.

Begränsningar

  • Webbservern för anpassad hanterare måste starta inom 60 sekunder.

Exempel

Se GitHub-lagringsplatsen för anpassade hanterarexempel för exempel på hur du implementerar funktioner på en mängd olika språk.

Felsökning och support

Spårningsloggning

Om din anpassade hanteringsprocess inte startar eller om den har problem med att kommunicera med Functions-värden kan du öka funktionsappens loggnivå till att Trace se fler diagnostikmeddelanden från värden.

Om du vill ändra funktionsappens standardloggnivå konfigurerar du logLevel inställningen i logging avsnittet i host.json.

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

Functions-värden matar ut extra loggmeddelanden, inklusive information som rör den anpassade hanteringsprocessen. Använd loggarna för att undersöka problem med att starta din anpassade hanteringsprocess eller anropa funktioner i din anpassade hanterare.

Lokalt skrivs loggar ut till konsolen.

I Azure kör du frågor mot Application Insights-spårningar för att visa loggmeddelandena. Om din app skapar en stor mängd loggar skickas endast en delmängd loggmeddelanden till Application Insights. Inaktivera sampling för att säkerställa att alla meddelanden loggas.

Testa anpassad hanterare isolerat

Anpassade hanteringsappar är en webbserverprocess, så det kan vara bra att starta den på egen hand och testa funktionsanrop genom att skicka falska HTTP-begäranden. Om du vill skicka HTTP-begäranden med nyttolaster måste du välja ett verktyg som skyddar dina data. Mer information finns i HTTP-testverktyg.

Du kan också använda den här strategin i DINA CI/CD-pipelines för att köra automatiserade tester på din anpassade hanterare.

Körningsmiljö

Anpassade hanterare körs i samma miljö som en typisk Azure Functions-app. Testa hanteraren för att se till att miljön innehåller alla beroenden som den behöver köra. För appar som kräver ytterligare beroenden kan du behöva köra dem med hjälp av en anpassad containeravbildning som finns i Azure Functions Premium-planen.

Få support

Om du behöver hjälp med en funktionsapp med anpassade hanterare kan du skicka en begäran via vanliga supportkanaler. Men på grund av de många olika språk som används för att skapa anpassade hanteringsappar är stödet inte obegränsat.

Stöd är tillgängligt om Functions-värden har problem med att starta eller kommunicera med den anpassade hanteringsprocessen. För problem som är specifika för det inre arbetet i din anpassade hanteringsprocess, till exempel problem med det valda språket eller ramverket, kan vårt supportteam inte ge hjälp i den här kontexten.

Nästa steg

Kom igång med att skapa en Azure Functions-app i Go eller Rust med snabbstarten för anpassade hanterare.