Processadores personalizados das Funções do Azure

Cada aplicação de Funções é executada por um processador específico do idioma. Embora Funções do Azure tenha muitas funcionalidades de processadores de idiomas por predefinição, existem casos em que poderá querer utilizar outros idiomas ou runtimes.

Os processadores personalizados são servidores Web leves que recebem eventos do anfitrião de Funções. Qualquer idioma que suporte primitivos HTTP pode implementar um processador personalizado.

Os processadores personalizados são mais adequados para situações em que pretende:

  • Implemente uma aplicação de funções num idioma que não seja atualmente oferecido fora da caixa, como Go ou Rust.
  • Implemente uma aplicação de funções num runtime que não esteja atualmente em destaque por predefinição, como o Deno.

Com processadores personalizados, pode utilizar acionadores e enlaces de entrada e saída através de pacotes de extensões.

Comece a utilizar Funções do Azure processadores personalizados com inícios rápidos em Go e Rust.

Descrição Geral

O diagrama seguinte mostra a relação entre o anfitrião de Funções e um servidor Web implementado como um processador personalizado.

Funções do Azure descrição geral do processador personalizado

  1. Cada evento aciona um pedido enviado para o anfitrião de Funções. Um evento é qualquer acionador suportado pelo Funções do Azure.
  2. Em seguida, o anfitrião de Funções emite um payload de pedido para o servidor Web. O payload contém dados de enlace de entrada e acionador e outros metadados para a função.
  3. O servidor Web executa a função individual e devolve um payload de resposta ao anfitrião de Funções.
  4. O anfitrião das Funções transmite dados da resposta aos enlaces de saída da função para processamento.

Uma aplicação Funções do Azure implementada como um processador personalizado tem de configurar os ficheiros host.json, local.settings.json e function.json de acordo com algumas convenções.

Estrutura da aplicação

Para implementar um processador personalizado, precisa dos seguintes aspetos para a sua aplicação:

  • Um ficheiro host.json na raiz da sua aplicação
  • Um ficheiro local.settings.json na raiz da sua aplicação
  • Um ficheiro 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 seguinte mostra o aspeto destes ficheiros no sistema de ficheiros para uma função chamada "MyQueueFunction" e um executável de processador personalizado com o nome 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 indica ao anfitrião de Funções para onde enviar pedidos ao apontar para um servidor Web capaz de processar eventos HTTP.

Um processador personalizado é definido ao configurar o ficheiro host.json com detalhes sobre como executar o servidor Web através da customHandler secção.

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

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

Utilize a arguments matriz para transmitir quaisquer argumentos para o executável. Os argumentos suportam a expansão de variáveis de ambiente (definições da aplicação) através %% da notação.

Também pode alterar o diretório de trabalho utilizado pelo executável com workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Suporte de enlaces

Os acionadores padrão, juntamente com os enlaces de entrada e saída, estão disponíveis ao referenciar pacotes de extensões no ficheiro host.json .

local.settings.json

local.settings.json define as definições da aplicação utilizadas ao executar a aplicação de funções localmente. Como pode conter segredos, local.settings.json deve ser excluído do controlo de origem. Em alternativa, no Azure, utilize as definições da aplicação.

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

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

Metadados da função

Quando utilizado com um processador personalizado, os conteúdos function.json não são diferentes de como definiria uma função em qualquer outro contexto. O único requisito é que os ficheiros function.json têm de estar numa pasta com o nome para corresponder ao nome da função.

O function.json seguinte configura uma função que tem um acionador de fila e um enlace de saída da fila. Uma vez que está numa pasta chamada MyQueueFunction, 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"
    }
  ]
}

Payload do pedido

Quando é recebida uma mensagem de fila, o anfitrião das Funções envia um pedido de mensagem HTTP ao processador personalizado com um payload no corpo.

O código seguinte representa um payload de pedido de exemplo. O payload inclui uma estrutura JSON com dois membros: Data e Metadata.

O Data membro inclui chaves que correspondem aos nomes de entrada e acionador conforme definido na matriz de enlaces no ficheiro function.json .

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

Payload de resposta

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

Chave de payload Tipo de dados Observações
Outputs objeto Contém valores de resposta conforme definido pela bindings matriz em function.json.

Por exemplo, se uma função estiver configurada com um enlace de saída de fila com o nome "myQueueOutput", Outputs contém uma chave com o nome myQueueOutput, que é definida pelo processador personalizado para as mensagens enviadas para a fila.
Logs matriz As mensagens aparecem nos registos de invocação de Funções.

Ao executar no Azure, as mensagens são apresentadas no Application Insights.
ReturnValue string Utilizado para fornecer uma resposta quando uma saída é configurada como $return no ficheiro function.json .

Este é um exemplo de um payload 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 processadores personalizados podem ser implementados em qualquer idioma que suporte a receção de eventos HTTP. Os exemplos seguintes mostram como implementar um processador personalizado com a linguagem de programação Go.

Função com enlaces

O cenário implementado neste exemplo apresenta uma função com o nome order que aceita um POST com um payload que representa uma encomenda de produto. À medida que uma encomenda é publicada na função, é criada uma mensagem de Armazenamento de Filas e é devolvida uma resposta HTTP.

Implementação

Numa pasta com o nome order, o ficheiro function.json configura a função acionada por 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"
    }
  ]
}

Esta função é definida como uma função acionada por HTTP que devolve uma resposta HTTP e gera uma mensagem de Armazenamento de filas .

Na raiz da aplicação, o ficheiro host.json está configurado para executar um ficheiro executável com o nome 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)"
  }
}

Este é o pedido HTTP enviado para o runtime das Funções.

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

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

Em seguida, o runtime de Funções enviará o seguinte pedido HTTP para o processador 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 do payload foram removidas por brevidade.

handler.exe é o programa de processador personalizado Go compilado que executa um servidor Web e responde a pedidos de invocação de funções do anfitrião de Funções.

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 processador personalizado executa um servidor Web para processar eventos HTTP e está definido para escutar pedidos através do FUNCTIONS_CUSTOMHANDLER_PORT.

Apesar de o anfitrião das Funções ter recebido um pedido HTTP original em /api/order, invoca o processador personalizado com o nome da função (o nome da pasta). Neste exemplo, a função é definida no caminho de /order. O anfitrião envia ao processador personalizado um pedido HTTP no caminho de /order.

À medida POST que os pedidos são enviados para esta função, os dados do acionador e os metadados da função estão disponíveis através do corpo do pedido HTTP. O corpo original do pedido HTTP pode ser acedido no .Data.req.Body

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

Este é um payload de exemplo que este processador devolve ao anfitrião de Funções.

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

Ao definir o message resultado como igual aos dados de encomenda recebidos do pedido, a função produz que ordena os dados para a fila configurada. O anfitrião de Funções também devolve a resposta HTTP configurada no res chamador.

Função apenas HTTP

Para funções acionadas por HTTP sem enlaces ou saídas adicionais, pode querer que o processador trabalhe diretamente com o pedido HTTP e a resposta em vez dos payloads de pedidos de resposta e pedidos de processador personalizados. Este comportamento pode ser configurado em host.json com a enableForwardingHttpRequest definição .

Importante

O principal objetivo da funcionalidade de processadores personalizados é ativar idiomas e runtimes que não têm atualmente suporte de primeira classe no Funções do Azure. Embora possa ser possível executar aplicações Web com processadores personalizados, Funções do Azure não é um proxy inverso padrão. Algumas funcionalidades, como a transmissão em fluxo de resposta, HTTP/2 e WebSockets, não estão disponíveis. Alguns componentes do pedido HTTP, como determinados cabeçalhos e rotas, podem ser restritos. A sua aplicação também pode sofrer um início a frio excessivo.

Para resolver estas circunstâncias, considere executar as suas aplicações Web no Serviço de Aplicações do Azure.

O exemplo seguinte demonstra como configurar uma função acionada por HTTP sem enlaces ou saídas adicionais. O cenário implementado neste exemplo apresenta uma função denominada hello que aceita um GET ou POST .

Implementação

Numa pasta com o nome hello, o ficheiro function.json configura a função acionada por HTTP.

hello/function.json

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

A função está configurada para aceitar pedidos GET e e POST o valor do resultado é fornecido através de um argumento chamado res.

Na raiz da aplicação, o ficheiro host.json está configurado para ser executado handler.exe e enableForwardingHttpRequest está definido como true.

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

Quando enableForwardingHttpRequest é true, o comportamento das funções apenas HTTP difere do comportamento predefinido dos processadores personalizados das seguintes formas:

  • O pedido HTTP não contém o payload de pedidos de processadores personalizados. Em vez disso, o anfitrião de Funções invoca o processador com uma cópia do pedido HTTP original.
  • O anfitrião das Funções invoca o processador com o mesmo caminho que o pedido original, incluindo quaisquer parâmetros de cadeia de consulta.
  • O anfitrião das Funções devolve uma cópia da resposta HTTP do processador como resposta ao pedido original.

Segue-se um pedido POST ao anfitrião de Funções. Em seguida, o anfitrião de Funções envia uma cópia do pedido para o processador personalizado no mesmo caminho.

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

{
  "message": "Hello World!"
}

O ficheiro handler.go de ficheiros 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 processador personalizado cria um servidor Web para processar eventos HTTP e está definido para escutar pedidos através do FUNCTIONS_CUSTOMHANDLER_PORT.

GET os pedidos são processados ao devolver uma cadeia e POST os pedidos têm acesso ao corpo do pedido.

A rota para a função order aqui é /api/hello, igual ao pedido original.

Nota

A FUNCTIONS_CUSTOMHANDLER_PORT não é a porta destinada ao público utilizada para chamar a função. Esta porta é utilizada pelo anfitrião de Funções para chamar o processador personalizado.

A implementar

Um processador personalizado pode ser implementado em todas as Funções do Azure opção de alojamento. Se o processador necessitar de dependências do sistema operativo ou da plataforma (como um runtime de linguagem), poderá ter de utilizar um contentor personalizado.

Ao criar uma aplicação de funções no Azure para processadores personalizados, recomendamos que selecione .NET Core como a pilha.

Para implementar uma aplicação de processador personalizada com o Funções do Azure Core Tools, execute o seguinte comando.

func azure functionapp publish $functionAppName

Nota

Certifique-se de que todos os ficheiros necessários para executar o processador personalizado estão na pasta e estão incluídos na implementação. Se o processador personalizado for um executável binário ou tiver dependências específicas da plataforma, certifique-se de que estes ficheiros correspondem à plataforma de implementação de destino.

Restrições

  • O servidor Web do processador personalizado tem de ser iniciado dentro de 60 segundos.

Amostras

Veja o repositório do GitHub de exemplos de processador personalizado para obter exemplos de como implementar funções numa variedade de idiomas diferentes.

Resolução de problemas e suporte

Registo de rastreio

Se o seu processo de processador personalizado não iniciar ou se tiver problemas ao comunicar com o anfitrião de Funções, pode aumentar o nível de registo da aplicação de funções para Trace ver mais mensagens de diagnóstico do anfitrião.

Para alterar o nível de registo predefinido da aplicação de funções, configure a logLevel definição na logging secção host.json.

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

O anfitrião das Funções produz mensagens de registo adicionais, incluindo informações relacionadas com o processo de processador personalizado. Utilize os registos para investigar problemas ao iniciar o processo de processador personalizado ou invocar funções no processador personalizado.

Localmente, os registos são impressos na consola do .

No Azure, consulte os rastreios do Application Insights para ver as mensagens de registo. Se a sua aplicação produzir um grande volume de registos, apenas um subconjunto de mensagens de registo é enviado para o Application Insights. Desative a amostragem para garantir que todas as mensagens são registadas.

Testar o processador personalizado isoladamente

As aplicações de processador personalizadas são um processo de servidor Web, pelo que pode ser útil iniciá-lo por si só e testar invocações de funções ao enviar pedidos HTTP fictícios com uma ferramenta como cURL ou Postman.

Também pode utilizar esta estratégia nos pipelines de CI/CD para executar testes automatizados no processador personalizado.

Ambiente de execução

Os processadores personalizados são executados no mesmo ambiente que uma aplicação de Funções do Azure típica. Teste o processador para garantir que o ambiente contém todas as dependências que precisa de executar. Para aplicações que necessitam de dependências adicionais, poderá ter de executá-las com uma imagem de contentor personalizada alojada no plano Funções do Azure Premium.

Obter suporte

Se precisar de ajuda numa aplicação de funções com processadores personalizados, pode submeter um pedido através de canais de suporte regulares. No entanto, devido à grande variedade de idiomas possíveis utilizados para criar aplicações de processadores personalizados, o suporte não é ilimitado.

O suporte está disponível se o anfitrião de Funções tiver problemas ao iniciar ou comunicar com o processo de processador personalizado. Para problemas específicos do funcionamento interno do seu processo de processador personalizado, como problemas com a linguagem ou arquitetura escolhida, a nossa Equipa de Suporte não consegue fornecer assistência neste contexto.

Passos seguintes

Comece a criar uma aplicação Funções do Azure em Go ou Rust com o início rápido dos processadores personalizados.