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.
- Cada evento dispara uma solicitação enviada ao host do Functions. Um evento é qualquer gatilho suportado pelo Azure Functions.
- 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.
- O servidor Web executa a função individual e retorna uma carga útil de resposta para o host Functions.
- 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:
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.Body
no .
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.