Azure Functions anpassade hanterare

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

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 som "out-of-the-box", till exempel Go eller Pra.
  • Implementera en funktionsapp i en körning som för närvarande inte är aktuell som standard, till exempel Deno.

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

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

Översikt

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

Azure Functions översikt över anpassad hanterare

  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 nyttolast för svar 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 konfigurerahost.jspå , local.settings.js på och function.jspå filer enligt några konventioner.

Programstruktur

Om du vill implementera en anpassad hanterare behöver du följande aspekter av ditt program:

  • En host.jspå fil i roten av din app
  • En local.settings.jspå fil i roten av din app
  • En function.jsfil för varje funktion (inuti en mapp som matchar funktionsnamnet)
  • Ett kommando, skript eller körbar fil som kör en webbserver

Följande diagram visar hur de här filerna ser ut i filsystemet för en funktion med namnet "MyQueueFunction" och en anpassad körbar hanterare med namnethandler.exe.

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

Konfiguration

Programmet konfigureras via -host.js och local.settings.jspå filer.

host.jspå

host.jspå 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 konfigurerahost.jspå filen med information om hur du kör webbservern via avsnittet customHandler .

{
  "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 arguments matrisen för att skicka 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äggspaketet ihost.jspå filen.

local.settings.json

local.settings.jspå definierar programinställningar som används när funktionsappen körs lokalt. Eftersom den kan innehålla hemligheter börlocal.settings.jspå undantas från källkontrollen. Använd programinställningar i stället i Azure.

För anpassade hanterare anger du FUNCTIONS_WORKER_RUNTIME till ilocal.settings.jsCustom .

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

Funktionsmetadata

När den används med en anpassad hanterarefunction.jsinnehållet inte annorlunda än hur du definierar en funktion i någon annan kontext. Det enda kravet är att function.jspå filer måste finnas i en mapp med namnet för att matcha funktionsnamnet.

Följande steg function.jsen 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.jspå

{
  "bindings": [
    {
      "name": "myQueueItem",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "messages-incoming",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "queue",
      "direction": "out",
      "queueName": "messages-outgoing",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

Begära 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 exempelnyttolast för begä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 ifunction.js filen.

Medlemmen Metadata innehåller metadata som genereras 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"
    }
  }
}

Nyttolast för svar

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

Nyttolastnyckel Datatyp Kommentarer
Outputs objekt Innehåller svarsvärden som definieras av bindings matrisen ifunction.jspå.

Om en funktion till exempel har konfigurerats med en köutdatabindning med namnet "myQueueOutput" innehåller den en nyckel med namnet , som anges av den anpassade hanteraren för de meddelanden som skickas till Outputs myQueueOutput 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 har konfigurerats $return som ifunction.js filen.

Det här är ett exempel på en nyttolast för svar.

{
  "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å alla språk som stöder mottagning av HTTP-händelser. I följande exempel visas hur du implementerar en anpassad hanterare med hjälp av programmeringsspråket Go.

Funktion med bindningar

Det scenario som implementeras i det här exemplet innehåller en funktion order med namnet som accepterar en med en POST nyttolast som representerar en produktbeställning. När en order publiceras till funktionen skapas ett Queue Storage och ett HTTP-svar returneras.

Implementering

I en mapp med namnet order konfigurerarfunction.jsfil den HTTP-utlösta funktionen.

order/function.jspå

{
  "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 Queue Storage-meddelande.

I appens rot konfigureras host.jsfil för att köra en körbar fil med namnet handler.exe ( i Linux eller handler macOS).

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

Det här är DEN 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": {
  }
}

Anteckning

Vissa delar av nyttolasten har tagits bort av utrymmes görande.

handler.exe är det kompilerade anpassade Go-hanterarprogrammet 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å anropar den den anpassade hanteraren med hjälp av /api/order funktionsnamnet (dess mappnamn). I det här exemplet definieras funktionen på sökvägen till /order . Värden skickar den anpassade hanteraren en HTTP-begäran på sökvägen till /order .

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 medlemmen innehåller ett JSON-värde där nycklarna matchar utdata enligt definitionen Outputs ifunction.jspå filen.

Det här är en exempelnyttolast 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 ställa in utdata som motsvarar orderdata som kom in från begäran matar funktionen ut dessa message orderdata till den konfigurerade kön. Functions-värden returnerar även HTTP-svaret som konfigurerats res i till anroparen.

Funktionen ENDAST HTTP

För HTTP-utlösta funktioner utan ytterligare bindningar eller utdata kanske du vill att hanteraren ska fungera direkt med HTTP-begäran och HTTP-svaret i stället för den anpassade hanterarens nyttolaster för begäran och svar. Det här beteendet kan konfigureras ihost.jspå med hjälp av enableForwardingHttpRequest inställningen.

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. Programmet kan också uppleva för hög kallstart.

För att hantera dessa omständigheter bör du överväga att köra dina webbappar på Azure App Service.

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

Implementering

I en mapp med namnet hello konfigurerarfunction.jspå filen den HTTP-utlösta funktionen.

hello/function.jspå

{
  "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 - POST och -begäranden och resultatvärdet tillhandahålls via ett argument med namnet res .

I roten av appen är host.jspå filen konfigurerad för att köras och är inställd handler.exeenableForwardingHttpRequest true .

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

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

  • HTTP-begäran innehåller inte den anpassade hanterarens begärandenyttolast. 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 en 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 en sträng returneras POST och begäranden har åtkomst till begärandetexten.

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

Anteckning

är FUNCTIONS_CUSTOMHANDLER_PORT inte den offentliga porten 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. En "anpassad" stack för anpassade hanterare kommer att läggas till i framtiden.

Om du vill distribuera en anpassad hanterarapp med Azure Functions Core Tools kör du följande kommando.

func azure functionapp publish $functionAppName

Anteckning

Se till 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 en binär körbar fil eller har plattformsspecifika beroenden ser du till att de här filerna matchar måldistributionsplattformen.

Begränsningar

  • Den anpassade hanterarwebbservern 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 för att se fler diagnostikmeddelanden från Trace värden.

Om du vill ändra standardloggnivån för funktionsappen konfigurerar logLevel du inställningen i avsnittet ihost.jslogging .

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

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

Lokalt skrivs loggar ut till konsolen.

I Azure kan du 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 se till att alla meddelanden loggas.

Testa anpassad hanterare isolerat

Anpassade hanterarappar är en webbserverprocess, så det kan vara bra att starta den på egen hand och testa funktionsanrop genom att skicka fingerade HTTP-begäranden med hjälp av ett verktyg som cURL eller Postman.

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 säkerställa 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 på Azure Functions Premium-plan.

Få support

Om du behöver hjälp med en funktionsapp med anpassade hanterare kan du skicka en begäran via vanliga supportkanaler. På grund av den mängd olika språk som används för att skapa anpassade hanterarappar är stödet dock 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 den anpassade hanteringsprocessens inre funktioner, 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 Azure Functions app i Go eller Ring med snabbstarten för anpassade hanterare.