Partilhar via


Processadores personalizados das Funções do Azure

Cada aplicativo Functions é executado por um manipulador específico do idioma. Embora o Azure Functions apresente muitos manipuladores de linguagem por padrão, há casos em que você pode querer usar outros idiomas ou tempos de execução.

Manipuladores personalizados são servidores Web leves que recebem eventos do host Functions. Qualquer linguagem que ofereça suporte a primitivas HTTP pode implementar um manipulador personalizado.

Os manipuladores personalizados são mais adequados para situações em que você deseja:

  • Implemente um aplicativo de função em um idioma que não é oferecido atualmente pronto para uso, como Go ou Rust.
  • Implemente um aplicativo de função em um tempo de execução que não esteja atualmente apresentado por padrão, como Deno.

Com manipuladores personalizados, você pode usar gatilhos e ligações de entrada e saída por meio de pacotes de extensão.

Introdução aos manipuladores personalizados do Azure Functions com inícios rápidos em Go e Rust.

Descrição geral

O diagrama a seguir mostra a relação entre o host Functions e um servidor Web implementado como um manipulador personalizado.

Visão geral do manipulador personalizado do Azure Functions

  1. Cada evento dispara uma solicitação enviada ao host do Functions. Um evento é qualquer gatilho suportado pelo Azure Functions.
  2. Em seguida, o host Functions emite uma carga útil de solicitação para o servidor Web. A carga útil contém dados de ligação de gatilho e entrada e outros metadados para a função.
  3. O servidor Web executa a função individual e retorna uma carga útil de resposta para o host Functions.
  4. O host Functions passa dados da resposta para as ligações de saída da função para processamento.

Um aplicativo do Azure Functions implementado como um manipulador personalizado deve configurar os arquivos de host.json, local.settings.json e function.json de acordo com algumas convenções.

Estrutura da aplicação

Para implementar um manipulador personalizado, você precisa dos seguintes aspetos para seu aplicativo:

  • Um arquivo host.json na raiz do seu aplicativo
  • Um arquivo local.settings.json na raiz do seu aplicativo
  • Um arquivo function.json para cada função (dentro de uma pasta que corresponde ao nome da função)
  • Um comando, script ou executável, que executa um servidor Web

O diagrama a seguir mostra como esses arquivos parecem no sistema de arquivos para uma função chamada "MyQueueFunction" e um executável manipulador personalizado chamado handler.exe.

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

Configuração

A aplicação é configurada através dos ficheiros host.json e local.settings.json .

host.json

host.json informa ao host do Functions para onde enviar solicitações apontando para um servidor Web capaz de processar eventos HTTP.

Um manipulador personalizado é definido configurando o arquivo host.json com detalhes sobre como executar o servidor Web através da customHandler seção .

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

A customHandler seção aponta para um destino conforme definido pelo defaultExecutablePath. O destino de execução pode ser um comando, executável ou arquivo onde o servidor Web é implementado.

Use a arguments matriz para passar quaisquer argumentos para o executável. Os argumentos suportam a expansão de variáveis de ambiente (configurações do aplicativo) usando %% notação.

Você também pode alterar o diretório de trabalho usado pelo executável com workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Suporte a ligações

Gatilhos padrão, juntamente com ligações de entrada e saída, estão disponíveis fazendo referência a pacotes de extensão em seu arquivo host.json .

local.settings.json

local.settings.json define as configurações do aplicativo usadas ao executar o aplicativo de função localmente. Como pode conter segredos, local.settings.json devem ser excluídos do controle do código-fonte. No Azure, use as configurações do aplicativo.

Para manipuladores personalizados, defina FUNCTIONS_WORKER_RUNTIME como Custom em local.settings.json.

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

Metadados da função

Quando usado com um manipulador personalizado, o conteúdo function.json não é diferente de como você definiria uma função em qualquer outro contexto. O único requisito é que function.json arquivos devem estar em uma pasta nomeada para corresponder ao nome da função.

A function.json a seguir configura uma função que tem um gatilho de fila e uma ligação de saída de fila. Como ele está em uma pasta chamada MyQueueFunction, ele define uma função chamada 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"
    }
  ]
}

Solicitar carga útil

Quando uma mensagem de fila é recebida, o host Functions envia uma solicitação HTTP post para o manipulador personalizado com uma carga no corpo.

O código a seguir representa uma carga útil de solicitação de exemplo. A carga útil inclui uma estrutura JSON com dois membros: Data e Metadata.

O Data membro inclui chaves que correspondem aos nomes de entrada e de gatilho, conforme definido na matriz bindings no arquivo function.json .

O Metadata membro inclui metadados gerados a partir da fonte do 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"
    }
  }
}

Carga útil de resposta

Por convenção, as respostas de função são formatadas como pares chave/valor. As chaves suportadas incluem:

Chave de carga útil Tipo de dados Observações
Outputs objeto Mantém valores de resposta conforme definido pela bindings matriz em function.json.

Por exemplo, se uma função é configurada com uma ligação de saída de fila chamada "myQueueOutput", então Outputs contém uma chave chamada myQueueOutput, que é definida pelo manipulador personalizado para as mensagens que são enviadas para a fila.
Logs matriz As mensagens aparecem nos logs de invocação do Functions.

Quando executadas no Azure, as mensagens aparecem no Application Insights.
ReturnValue string Usado para fornecer uma resposta quando uma saída é configurada como $return no arquivo function.json.

Este é um exemplo de uma carga útil de resposta.

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

Exemplos

Os manipuladores personalizados podem ser implementados em qualquer linguagem que ofereça suporte ao recebimento de eventos HTTP. Os exemplos a seguir mostram como implementar um manipulador personalizado usando a linguagem de programação Go.

Função com ligações

O cenário implementado neste exemplo apresenta uma função chamada order que aceita um POST com uma carga útil que representa uma ordem de produto. À medida que um pedido é postado na função, uma mensagem de armazenamento de fila é criada e uma resposta HTTP é retornada.

Implementação

Em uma pasta chamada ordem, o arquivo function.json configura a função acionada por HTTP.

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

Essa função é definida como uma função acionada por HTTP que retorna uma resposta HTTP e gera uma mensagem de armazenamento de fila.

Na raiz do aplicativo, o arquivo host.json é configurado para executar um arquivo executável chamado handler.exe (handler no Linux ou macOS).

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

Esta é a solicitação HTTP enviada para o tempo de execução do Functions.

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

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

O tempo de execução do Functions enviará a seguinte solicitação HTTP para o manipulador personalizado:

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

Algumas partes da carga útil foram removidas por uma questão de brevidade.

handler.exe é o programa de manipulador personalizado Go compilado que executa um servidor Web e responde a solicitações de invocação de função do host 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))
}

Neste exemplo, o manipulador personalizado executa um servidor Web para manipular eventos HTTP e está definido para ouvir solicitações por meio do FUNCTIONS_CUSTOMHANDLER_PORT.

Embora o host Functions tenha recebido uma solicitação HTTP original no /api/order, ele invoca o manipulador personalizado usando o nome da função (seu nome de pasta). Neste exemplo, a função é definida no caminho de /order. O host envia ao manipulador personalizado uma solicitação HTTP no caminho do /order.

À medida POST que as solicitações são enviadas para essa função, os dados de gatilho e os metadados da função ficam disponíveis por meio do corpo da solicitação HTTP. O corpo da solicitação HTTP original pode ser acessado Data.req.Bodyno .

A resposta da função é formatada em pares chave/valor onde o Outputs membro detém um valor JSON onde as chaves correspondem às saídas conforme definido no arquivo function.json .

Este é um exemplo de carga útil que esse manipulador retorna ao host Functions.

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

Ao definir a message saída igual aos dados de ordem que vieram da solicitação, a função envia esses dados de ordem para a fila configurada. O host Functions também retorna a resposta HTTP configurada no res chamador.

Função somente HTTP

Para funções acionadas por HTTP sem ligações ou saídas adicionais, você pode querer que seu manipulador trabalhe diretamente com a solicitação e resposta HTTP em vez das cargas úteis de solicitação e resposta do manipulador personalizado. Esse comportamento pode ser configurado em host.json usando a enableForwardingHttpRequest configuração.

Importante

O objetivo principal do recurso de manipuladores personalizados é habilitar linguagens e tempos de execução que atualmente não têm suporte de primeira classe no Azure Functions. Embora seja possível executar aplicativos Web usando manipuladores personalizados, o Azure Functions não é um proxy reverso padrão. Alguns recursos como streaming de resposta, HTTP/2 e WebSockets não estão disponíveis. Alguns componentes da solicitação HTTP, como determinados cabeçalhos e rotas, podem ser restritos. A sua aplicação também pode ter arranque a frio excessivo.

Para resolver essas circunstâncias, considere executar seus aplicativos Web no Serviço de Aplicativo do Azure.

O exemplo a seguir demonstra como configurar uma função acionada por HTTP sem ligações ou saídas adicionais. O cenário implementado neste exemplo apresenta uma função chamada hello que aceita um GET ou POST .

Implementação

Em uma pasta chamada hello, o arquivo function.json configura a função acionada por HTTP.

Olá/function.json

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

A função é configurada para aceitar solicitações GET e POST solicitações e o valor do resultado é fornecido por meio de um argumento chamado res.

Na raiz do aplicativo, o arquivo host.json está configurado para ser executado handler.exe e enableForwardingHttpRequest definido como true.

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

Quando enableForwardingHttpRequest é true, o comportamento de funções somente HTTP difere do comportamento padrão de manipuladores personalizados das seguintes maneiras:

  • A solicitação HTTP não contém a carga útil de solicitação de manipuladores personalizados. Em vez disso, o host Functions invoca o manipulador com uma cópia da solicitação HTTP original.
  • O host Functions invoca o manipulador com o mesmo caminho da solicitação original, incluindo quaisquer parâmetros de cadeia de caracteres de consulta.
  • O host Functions retorna uma cópia da resposta HTTP do manipulador como a resposta à solicitação original.

A seguir está uma solicitação POST para o host de funções. Em seguida, o host Functions envia uma cópia da solicitação para o manipulador personalizado no mesmo caminho.

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

{
  "message": "Hello World!"
}

O arquivo handler.go implementa um servidor Web e uma função 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))
}

Neste exemplo, o manipulador personalizado cria um servidor Web para manipular eventos HTTP e é definido para ouvir solicitações por meio do FUNCTIONS_CUSTOMHANDLER_PORT.

GET As solicitações são tratadas retornando uma cadeia de caracteres e POST as solicitações têm acesso ao corpo da solicitação.

A rota para a função de ordem aqui é /api/hello, igual à solicitação original.

Nota

A FUNCTIONS_CUSTOMHANDLER_PORT porta não é voltada para o público usada para chamar a função. Essa porta é usada pelo host Functions para chamar o manipulador personalizado.

Implementação

Um manipulador personalizado pode ser implantado em cada opção de hospedagem do Azure Functions. Se o manipulador exigir dependências do sistema operacional ou da plataforma (como um language runtime), talvez seja necessário usar um contêiner personalizado.

Ao criar um aplicativo de função no Azure para manipuladores personalizados, recomendamos que você selecione .NET Core como a pilha.

Para implantar um aplicativo manipulador personalizado usando as Ferramentas Principais do Azure Functions, execute o seguinte comando.

func azure functionapp publish $functionAppName

Nota

Verifique se todos os arquivos necessários para executar seu manipulador personalizado estão na pasta e incluídos na implantação. Se o manipulador personalizado for um executável binário ou tiver dependências específicas da plataforma, verifique se esses arquivos correspondem à plataforma de implantação de destino.

Restrições

  • O servidor Web do manipulador personalizado precisa ser iniciado dentro de 60 segundos.

Exemplos

Consulte os exemplos de repositório GitHub do manipulador personalizado para obter exemplos de como implementar funções em uma variedade de idiomas diferentes.

Resolução de problemas e suporte

Registo de rastreio

Se o processo do manipulador personalizado falhar ao iniciar ou se tiver problemas de comunicação com o host do Functions, você poderá aumentar o nível de log do aplicativo de função para Trace ver mais mensagens de diagnóstico do host.

Para alterar o nível de log padrão do aplicativo de função, defina a logLevel logging configuração na seção de host.json.

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

O host Functions emite mensagens de log extras, incluindo informações relacionadas ao processo do manipulador personalizado. Use os logs para investigar problemas ao iniciar o processo do manipulador personalizado ou invocar funções no manipulador personalizado.

Localmente, os logs são impressos no console.

No Azure, consulte rastreamentos do Application Insights para exibir as mensagens de log. Se seu aplicativo produzir um grande volume de logs, apenas um subconjunto de mensagens de log será enviado para o Application Insights. Desative a amostragem para garantir que todas as mensagens sejam registradas.

Testar manipulador personalizado isoladamente

Os aplicativos manipuladores personalizados são um processo de servidor Web, portanto, pode ser útil iniciá-lo por conta própria e testar invocações de função enviando solicitações HTTP simuladas. Para enviar solicitações HTTP com cargas úteis, certifique-se de escolher uma ferramenta que mantenha seus dados seguros. Para obter mais informações, consulte Ferramentas de teste HTTP.

Você também pode usar essa estratégia em seus pipelines de CI/CD para executar testes automatizados em seu manipulador personalizado.

Ambiente de execução

Os manipuladores personalizados são executados no mesmo ambiente que um aplicativo típico do Azure Functions. Teste seu manipulador para garantir que o ambiente contenha todas as dependências necessárias para ser executado. Para aplicativos que exigem dependências adicionais, talvez seja necessário executá-los usando uma imagem de contêiner personalizada hospedada no plano Premium do Azure Functions.

Obter suporte

Se precisar de ajuda em um aplicativo de função com manipuladores personalizados, você pode enviar uma solicitação por meio de canais de suporte regulares. No entanto, devido à grande variedade de linguagens possíveis usadas para criar aplicativos de manipuladores personalizados, o suporte não é ilimitado.

O suporte estará disponível se o host Functions tiver problemas para iniciar ou se comunicar com o processo do manipulador personalizado. Para problemas específicos do funcionamento interno do seu processo de manipulador personalizado, como problemas com a linguagem ou estrutura escolhida, nossa equipe de suporte não pode fornecer assistência neste contexto.

Próximos passos

Comece a criar um aplicativo do Azure Functions em Go ou Rust com o início rápido de manipuladores personalizados.