Niestandardowe programy obsługi w usłudze Azure Functions

Każda aplikacja usługi Functions jest wykonywana przez program obsługi specyficzny dla języka. Chociaż Azure Functions obsługuje wiele języków domyślnie, istnieją przypadki, w których można użyć innych języków lub środowisk uruchomieniowych.

Niestandardowe programy obsługi to lekkie serwery internetowe, które odbierają zdarzenia z hosta usługi Functions. Każdy język obsługujący typy pierwotne HTTP może zaimplementować niestandardową procedurę obsługi.

Niestandardowe programy obsługi najlepiej sprawdzają się w sytuacjach, w których chcesz:

  • Zaimplementuj aplikację funkcji w języku, który nie jest obecnie oferowany jako gotowe, na przykład Go lub Rust.
  • Zaimplementuj aplikację funkcji w środowisku uruchomieniowym, który nie jest obecnie domyślnie polecany, na przykład Deno.

Za pomocą niestandardowych procedur obsługi można używać wyzwalaczy oraz powiązań wejściowych i wyjściowych za pośrednictwem pakietów rozszerzeń.

Rozpocznij pracę z Azure Functions niestandardowymi procedurami obsługi, korzystając z przewodników Szybki start w języku Go i Rust.

Omówienie

Na poniższym diagramie przedstawiono relację między hostem usługi Functions a serwerem internetowym zaimplementowanym jako niestandardowy program obsługi.

Omówienie niestandardowego programu obsługi Azure Functions

  1. Każde zdarzenie wyzwala żądanie wysyłane do hosta usługi Functions. Zdarzenie to dowolny wyzwalacz obsługiwany przez Azure Functions.
  2. Następnie host usługi Functions wysyła ładunek żądania do serwera internetowego. Ładunek zawiera dane wyzwalacza i powiązania wejściowego oraz inne metadane dla funkcji.
  3. Serwer internetowy wykonuje pojedynczą funkcję i zwraca ładunek odpowiedzi do hosta usługi Functions.
  4. Host usługi Functions przekazuje dane z odpowiedzi na powiązania wyjściowe funkcji do przetwarzania.

Aplikacja Azure Functions zaimplementowana jako niestandardowa procedura obsługi musi skonfigurować pliki host.json, local.settings.json i function.json zgodnie z kilkoma konwencjami.

Struktura aplikacji

Do zaimplementowania niestandardowego programu obsługi potrzebne są następujące aspekty aplikacji:

  • Plik host.json w katalogu głównym aplikacji
  • Plik local.settings.json w katalogu głównym aplikacji
  • Plik function.json dla każdej funkcji (wewnątrz folderu zgodnego z nazwą funkcji)
  • Polecenie, skrypt lub plik wykonywalny, który uruchamia serwer internetowy

Na poniższym diagramie przedstawiono wygląd tych plików w systemie plików dla funkcji o nazwie "MyQueueFunction" i pliku wykonywalnego niestandardowego programu obsługi o nazwie handler.exe.

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

Konfiguracja

Aplikacja jest konfigurowana za pośrednictwem plików host.json i local.settings.json .

host.json

Host.json informuje hosta usługi Functions, gdzie wysyłać żądania, wskazując serwer internetowy, który może przetwarzać zdarzenia HTTP.

Niestandardowa procedura obsługi jest definiowana przez skonfigurowanie pliku host.json ze szczegółowymi informacjami na temat uruchamiania serwera internetowego customHandler za pośrednictwem sekcji .

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

Sekcja customHandler wskazuje element docelowy zgodnie z definicją elementu defaultExecutablePath. Obiektem docelowym wykonywania może być polecenie, plik wykonywalny lub plik, w którym zaimplementowano serwer internetowy.

Użyj tablicy arguments , aby przekazać wszystkie argumenty do pliku wykonywalnego. Argumenty obsługują rozszerzanie zmiennych środowiskowych (ustawień aplikacji) przy użyciu %% notacji.

Możesz również zmienić katalog roboczy używany przez plik wykonywalny za pomocą polecenia workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Obsługa powiązań

Wyzwalacze standardowe wraz z powiązaniami wejściowymi i wyjściowymi są dostępne przez odwoływanie się do pakietów rozszerzeń w pliku host.json .

local.settings.json

local.settings.json definiuje ustawienia aplikacji używane podczas lokalnego uruchamiania aplikacji funkcji. Ponieważ może zawierać wpisy tajne, plik local.settings.json powinien zostać wykluczony z kontroli źródła. Zamiast tego na platformie Azure użyj ustawień aplikacji.

W przypadku niestandardowych procedur obsługi ustaw wartość FUNCTIONS_WORKER_RUNTIMECustom na w pliku local.settings.json.

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

Metadane funkcji

W przypadku użycia z niestandardowym programem obsługi zawartość function.json nie różni się od sposobu definiowania funkcji w innym kontekście. Jedynym wymaganiem jest to, że pliki function.json muszą znajdować się w folderze o nazwie , aby pasować do nazwy funkcji.

Poniższy plik function.json konfiguruje funkcję, która ma wyzwalacz kolejki i powiązanie wyjściowe kolejki. Ponieważ znajduje się on w folderze o nazwie MyQueueFunction, definiuje funkcję o nazwie 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"
    }
  ]
}

Żądanie ładunku

Po odebraniu komunikatu w kolejce host usługi Functions wysyła żądanie POST HTTP do niestandardowej procedury obsługi z ładunkiem w treści.

Poniższy kod reprezentuje przykładowy ładunek żądania. Ładunek zawiera strukturę JSON z dwoma elementami członkowskimi: Data i Metadata.

Element Data członkowski zawiera klucze zgodne z nazwami danych wejściowych i wyzwalaczy zgodnie z definicją w tablicy powiązań w pliku function.json .

Element Metadata członkowski zawiera metadane wygenerowane ze źródła zdarzeń.

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

Ładunek odpowiedzi

Zgodnie z konwencją odpowiedzi funkcji są formatowane jako pary klucz/wartość. Obsługiwane klucze obejmują:

Klucz ładunku Typ danych Uwagi
Outputs object Przechowuje wartości odpowiedzi zdefiniowane przez tablicę bindings w pliku function.json.

Jeśli na przykład funkcja jest skonfigurowana za pomocą powiązania wyjściowego kolejki o nazwie "myQueueOutput", Outputs zawiera klucz o nazwie myQueueOutput, który jest ustawiany przez niestandardową procedurę obsługi do komunikatów wysyłanych do kolejki.
Logs array Komunikaty są wyświetlane w dziennikach wywołania funkcji.

Podczas uruchamiania na platformie Azure komunikaty są wyświetlane w usłudze Application Insights.
ReturnValue ciąg Służy do dostarczania odpowiedzi, gdy dane wyjściowe są skonfigurowane tak jak $return w pliku function.json .

Jest to przykład ładunku odpowiedzi.

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

Przykłady

Niestandardowe programy obsługi można zaimplementować w dowolnym języku, który obsługuje odbieranie zdarzeń HTTP. W poniższych przykładach pokazano, jak zaimplementować niestandardową procedurę obsługi przy użyciu języka programowania Go.

Funkcja z powiązaniami

Scenariusz zaimplementowany w tym przykładzie zawiera funkcję o nazwie order , która akceptuje POST element z ładunkiem reprezentującym zamówienie produktu. Gdy zamówienie jest publikowane w funkcji, tworzony jest komunikat usługi Queue Storage i zwracana jest odpowiedź HTTP.

Implementacja

W folderze o nazwie order plik function.json konfiguruje funkcję wyzwalaną przez protokół 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"
    }
  ]
}

Ta funkcja jest zdefiniowana jako funkcja wyzwalana przez protokół HTTP , która zwraca odpowiedź HTTP i zwraca komunikat usługi Queue Storage .

W katalogu głównym aplikacji plik host.json jest skonfigurowany do uruchamiania pliku wykonywalnego o nazwie handler.exe (handler w systemie Linux lub macOS).

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

Jest to żądanie HTTP wysyłane do środowiska uruchomieniowego usługi Functions.

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

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

Środowisko uruchomieniowe usługi Functions wyśle następujące żądanie HTTP do niestandardowego programu obsługi:

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

Uwaga

Niektóre części ładunku zostały usunięte w celu zwięzłości.

handler.exe to skompilowany niestandardowy program obsługi języka Go, który uruchamia serwer internetowy i odpowiada na żądania wywołania funkcji z hosta usługi Functions.

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

W tym przykładzie program obsługi niestandardowej uruchamia serwer internetowy do obsługi zdarzeń HTTP i jest ustawiony do nasłuchiwania żądań za pośrednictwem .FUNCTIONS_CUSTOMHANDLER_PORT

Mimo że host usługi Functions otrzymał oryginalne żądanie HTTP pod /api/orderadresem , wywołuje niestandardową procedurę obsługi przy użyciu nazwy funkcji (nazwy folderu). W tym przykładzie funkcja jest definiowana na ścieżce ./order Host wysyła niestandardowy program obsługi żądania HTTP w ścieżce ./order

W miarę POST wysyłania żądań do tej funkcji dane wyzwalacza i metadane funkcji są dostępne za pośrednictwem treści żądania HTTP. Dostęp do oryginalnej treści żądania HTTP można uzyskać w ładunku Data.req.Body.

Odpowiedź funkcji jest formatowana na pary klucz/wartość, w których Outputs element członkowski zawiera wartość JSON, gdzie klucze pasują do danych wyjściowych zdefiniowanych w pliku function.json .

Jest to przykładowy ładunek zwracany przez tę procedurę obsługi do hosta usługi Functions.

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

message Ustawiając dane wyjściowe równe danym zamówienia, które pochodzą z żądania, funkcja zwraca dane, które nakazują dane do skonfigurowanej kolejki. Host usługi Functions zwraca również odpowiedź HTTP skonfigurowaną w res elemecie wywołującym.

Funkcja tylko http

W przypadku funkcji wyzwalanych przez protokół HTTP bez dodatkowych powiązań ani danych wyjściowych program obsługi może pracować bezpośrednio z żądaniem HTTP i odpowiedzią zamiast niestandardowych ładunków żądania i odpowiedzi programu obsługi. To zachowanie można skonfigurować w pliku host.json przy użyciu enableForwardingHttpRequest ustawienia.

Ważne

Głównym celem funkcji obsługi niestandardowej jest włączenie języków i środowisk uruchomieniowych, które nie mają obecnie pierwszej klasy obsługi w Azure Functions. Chociaż może być możliwe uruchamianie aplikacji internetowych przy użyciu niestandardowych procedur obsługi, Azure Functions nie jest standardowym zwrotnym serwerem proxy. Niektóre funkcje, takie jak przesyłanie strumieniowe odpowiedzi, protokół HTTP/2 i zestawy WebSocket, są niedostępne. Niektóre składniki żądania HTTP, takie jak niektóre nagłówki i trasy, mogą być ograniczone. Aplikacja może również napotkać nadmierny zimny start.

Aby rozwiązać te kwestie, rozważ uruchomienie aplikacji internetowych w Azure App Service.

W poniższym przykładzie pokazano, jak skonfigurować funkcję wyzwalaną przez protokół HTTP bez dodatkowych powiązań ani danych wyjściowych. Scenariusz zaimplementowany w tym przykładzie zawiera funkcję o nazwie hello , która akceptuje element GET lub POST .

Implementacja

W folderze o nazwie hello plik function.json konfiguruje funkcję wyzwalaną przez protokół HTTP.

hello/function.json

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

Funkcja jest skonfigurowana tak, aby akceptowała żądania GET , a POST wartość wyniku jest dostarczana za pośrednictwem argumentu o nazwie res.

W katalogu głównym aplikacji plik host.json jest skonfigurowany do uruchamiania handler.exe i enableForwardingHttpRequest jest ustawiony na truewartość .

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

Gdy enableForwardingHttpRequest jest to true, zachowanie funkcji tylko HTTP różni się od domyślnego zachowania niestandardowych procedur obsługi w następujący sposób:

  • Żądanie HTTP nie zawiera niestandardowego ładunku żądania obsługi. Zamiast tego host usługi Functions wywołuje procedurę obsługi z kopią oryginalnego żądania HTTP.
  • Host usługi Functions wywołuje procedurę obsługi z tą samą ścieżką co oryginalne żądanie, w tym wszystkie parametry ciągu zapytania.
  • Host usługi Functions zwraca kopię odpowiedzi HTTP programu obsługi jako odpowiedź na oryginalne żądanie.

Poniżej przedstawiono żądanie POST dla hosta usługi Functions. Następnie host usługi Functions wysyła kopię żądania do procedury obsługi niestandardowej w tej samej ścieżce.

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

{
  "message": "Hello World!"
}

Plik handler.go implementuje serwer internetowy i funkcję 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))
}

W tym przykładzie program obsługi niestandardowej tworzy serwer internetowy do obsługi zdarzeń HTTP i jest ustawiony na nasłuchiwanie żądań za pośrednictwem polecenia FUNCTIONS_CUSTOMHANDLER_PORT.

GET żądania są obsługiwane przez zwracanie ciągu, a POST żądania mają dostęp do treści żądania.

Trasa funkcji order w tym miejscu jest /api/hellotaka sama jak oryginalne żądanie.

Uwaga

Nie FUNCTIONS_CUSTOMHANDLER_PORT jest to publiczny port używany do wywoływania funkcji. Ten port jest używany przez hosta usługi Functions do wywoływania niestandardowej procedury obsługi.

Wdrażanie

Program obsługi niestandardowej można wdrożyć w każdej opcji hostingu Azure Functions. Jeśli program obsługi wymaga zależności systemu operacyjnego lub platformy (na przykład środowiska uruchomieniowego języka), może być konieczne użycie niestandardowego kontenera.

Podczas tworzenia aplikacji funkcji na platformie Azure dla niestandardowych procedur obsługi zalecamy wybranie platformy .NET Core jako stosu.

Aby wdrożyć niestandardową aplikację obsługi przy użyciu narzędzi Azure Functions Core Tools, uruchom następujące polecenie.

func azure functionapp publish $functionAppName

Uwaga

Upewnij się, że wszystkie pliki wymagane do uruchomienia niestandardowej procedury obsługi znajdują się w folderze i uwzględnione we wdrożeniu. Jeśli niestandardowa procedura obsługi jest binarnym plikiem wykonywalnym lub ma zależności specyficzne dla platformy, upewnij się, że te pliki są zgodne z docelową platformą wdrażania.

Ograniczenia

  • Serwer internetowy programu obsługi niestandardowej musi uruchomić się w ciągu 60 sekund.

Przykłady

Zapoznaj się z przykładami niestandardowego programu obsługi repozytorium GitHub , aby zapoznać się z przykładami implementowania funkcji w różnych językach.

Rozwiązywanie problemów i pomoc techniczna

Rejestrowanie śledzenia

Jeśli nie można uruchomić niestandardowego procesu obsługi lub jeśli wystąpią problemy z komunikacją z hostem usługi Functions, możesz zwiększyć poziom dziennika aplikacji funkcji, aby wyświetlić Trace więcej komunikatów diagnostycznych z hosta.

Aby zmienić domyślny poziom dziennika aplikacji funkcji, skonfiguruj logLevel ustawienie w logging sekcji pliku host.json.

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

Host usługi Functions generuje dodatkowe komunikaty dziennika, w tym informacje związane z niestandardowym procesem obsługi. Użyj dzienników, aby zbadać problemy podczas uruchamiania niestandardowego procesu obsługi lub wywoływania funkcji w niestandardowej procedurze obsługi.

Dzienniki są drukowane lokalnie w konsoli programu .

Na platformie Azure wykonaj zapytania dotyczące śladów usługi Application Insights , aby wyświetlić komunikaty dziennika. Jeśli aplikacja generuje dużą liczbę dzienników, do usługi Application Insights są wysyłane tylko podzbiór komunikatów dziennika. Wyłącz próbkowanie , aby upewnić się, że wszystkie komunikaty są rejestrowane.

Testowanie niestandardowej procedury obsługi w izolacji

Niestandardowe aplikacje obsługi są procesem serwera internetowego, dlatego warto uruchomić je na własną rękę i wywołania funkcji testowej, wysyłając pozorowane żądania HTTP przy użyciu narzędzia, takiego jak cURL lub Postman.

Możesz również użyć tej strategii w potokach ciągłej integracji/ciągłego wdrażania do uruchamiania testów automatycznych w niestandardowej procedurze obsługi.

Środowisko wykonywania

Niestandardowe programy obsługi działają w tym samym środowisku co typowa aplikacja Azure Functions. Przetestuj procedurę obsługi, aby upewnić się, że środowisko zawiera wszystkie zależności, które należy uruchomić. W przypadku aplikacji, które wymagają dodatkowych zależności, może być konieczne ich uruchomienie przy użyciu niestandardowego obrazu kontenera hostowanego w planie Azure Functions Premium.

Uzyskiwanie pomocy technicznej

Jeśli potrzebujesz pomocy dotyczącej aplikacji funkcji z niestandardowymi procedurami obsługi, możesz przesłać żądanie za pośrednictwem zwykłych kanałów pomocy technicznej. Jednak ze względu na szeroką gamę możliwych języków używanych do tworzenia niestandardowych aplikacji obsługi obsługa nie jest nieograniczona.

Obsługa jest dostępna, jeśli host usługi Functions ma problemy z uruchamianiem lub komunikowaniem się z niestandardowym procesem obsługi. W przypadku problemów specyficznych dla wewnętrznej pracy niestandardowego procesu obsługi, takich jak problemy z wybranym językiem lub strukturą, nasz zespół pomocy technicznej nie może udzielić pomocy w tym kontekście.

Następne kroki

Rozpocznij tworzenie aplikacji Azure Functions w języku Go lub Rust przy użyciu niestandardowego przewodnika Szybki start dla procedur obsługi.