Пользовательские обработчики Функций Azure

Каждое приложение-функция выполняется обработчиком конкретного языка. Поскольку Функции Azure поддерживают много языковых обработчиков по умолчанию, в некоторых случаях могут потребоваться другие языки или среды выполнения.

Пользовательские обработчики представляют собой упрощенные веб-серверы, которые получают события от узла Функций. Пользовательский обработчик можно реализовать на любом языке, который поддерживает примитивы HTTP.

Пользовательские обработчики лучше всего подходят для следующих ситуаций.

  • Реализация приложения-функции на языке, который в настоящее время не входит в число стандартных компонентов, например Go или Rust.
  • Реализация приложения-функции в среде выполнения, которая в настоящее время не входит в число рекомендуемых по умолчанию, например Deno.

С помощью пользовательских обработчиков можно использовать триггеры, входные и выходные привязки посредством пакетов расширений.

Приступая к работе с пользовательскими обработчиками Функций Azure, ознакомьтесь с краткими руководствами по Go и Rust.

Обзор

На следующей схеме показана связь между узлом Функций и веб-сервером, реализованным в качестве пользовательского обработчика.

Общие сведения о пользовательских обработчиках Функций Azure

  1. Каждое событие формирует запрос к узлу Функций. Событие — это любой триггер, поддерживаемый функциями Azure.
  2. Узел функций передает полезные данные запроса на веб-сервер. Полезные данные содержат данные триггера и входные данные привязки, а также другие метаданные для функции.
  3. Веб-сервер выполняет отдельную функцию и возвращает полезные данные ответа на узел Функций.
  4. Узел Функций передает данные из ответа в выходные привязки функции для обработки.

Приложение "Функции Azure", реализованное как пользовательский обработчик, должно настраивать файлы host.json, local.settings.json и function.json в соответствии с некоторыми соглашениями.

Структура приложений

Чтобы реализовать пользовательский обработчик, для вашего приложения потребуется наличие следующих объектов.

  • Файл host.json в корне файловой системы вашего приложения
  • Файл local.settings.json файловой системы вашего приложения
  • Файл function.json для каждой функции (в папке, соответствующей имени функции)
  • Команда, сценарий или исполняемый файл, который запускает веб-сервер

На следующей схеме показано, как эти файлы выглядят в файловой системе для функции MyQueueFunction и пользовательского исполняемого файла обработчика с именемhandler.exe.

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

Конфигурация

Приложение настраивается с помощью файлов host.json и local.settings.json.

host.json

Файл host.json указывает узлу Функций, куда отправлять запросы, указывая на веб-сервер, способный обрабатывать события HTTP.

Пользовательский обработчик определяется в файле host.json с подробными сведениями о способе запуска веб-сервера с помощью раздела customHandler.

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

Раздел customHandler указывает на целевой объект, как определено в defaultExecutablePath. Целевым объектом выполнения может быть команда, исполняемый файл или файл, в котором реализован веб-сервер.

Используйте массив arguments для передачи аргументов в исполняемый файл. Аргументы поддерживают расширение переменных среды (параметров приложения) с помощью нотации %%.

Кроме того, можно изменить рабочую папку, используемую исполняемым файлом на workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Поддержка привязок

Стандартные триггеры, а также входные и выходные привязки доступны по ссылке на пакеты расширений в вашем файле host.json.

local.settings.json

В файле local.settings.json определяются параметры приложения, используемые при локальном выполнении приложения-функции. Так как в файле могут содержаться секреты, файл local.settings.js необходимо исключить из системы управления версиями. В Azure вместо этого используйте параметры приложения.

Для пользовательских обработчиков установите для параметра FUNCTIONS_WORKER_RUNTIME значение Custom в файле local.settings.json.

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

Метаданные функции

При использовании с пользовательским обработчиком содержимое файла function.json не отличается от того, как вы определите функцию в любом другом контексте. Единственное требование заключается в том, чтобы файл function.json находился в папке с именем, совпадающей с именем функции.

В следующем файле function.jsоn настраивается функция, которая содержит триггер очереди и выходную привязку очереди. Так как файл находится в папке с именем MyQueueFunction, он определяет функцию с именем 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"
    }
  ]
}

Полезные данные запроса

При получении сообщения очереди узел Функций отправляет запрос HTTP POST в пользовательский обработчик с полезной нагрузкой в тексте.

Следующий код представляет пример полезных данных запроса выборки. Полезные данные включают структуру JSON с двумя элементами: Data и Metadata.

DataЭлемент содержит ключи, соответствующие именам входных данных и триггера, как определено в массиве привязок в файле function.json.

MetadataЭлемент содержит метаданные, созданные из источника событий.

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

Полезные данные ответа

По соглашению ответы функций форматируются в пары "ключ/значение". Поддерживаются следующие ключи.

Ключ полезных данных Тип данных Remarks
Outputs object Содержит значения ответа, определенные в виде массива bindings в файле function.json.

Например, если функция настраивается с выходной привязкой очереди с именем myQueueOutput, то Outputs содержит ключ с именем myQueueOutput, который задается пользовательским обработчиком для сообщений, передаваемых в очередь.
Logs array Сообщения отображаются в журналах вызова Функций.

При работе в Azure сообщения отображаются в Application Insights.
ReturnValue строка Используется для предоставления ответа, когда выходные данные настраиваются как $return в файле function.json.

Это пример полезных данных ответа.

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

Примеры

Пользовательские обработчики могут реализовываться на любом языке, поддерживающем получение HTTP-событий. В следующих примерах показан способ реализации пользовательского обработчика на языке программирования Go.

Функция с привязками

Сценарий, реализованный в этом примере, использует функцию с именем order, которая принимает объект POST с полезной нагрузкой, представляющей заказ продукта. Когда в функцию отправляется заказ, создается сообщение Хранилища очередей и возвращается ответ HTTP.

Реализация

В папке с именем order файл function.json содержит настройки функции для триггеров 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"
    }
  ]
}

Эта функция определяется как функция, для триггеров HTTP, которая возвращает ответ HTTP и выводит сообщение Хранилища очередей.

Файл host.json, находящийся в корне файловой системы приложения, настраивается для запуска исполняемого файла с именем handler.exe (handler в Linux или macOS).

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

Это HTTP-запрос, отправленный в среду выполнения Функций.

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

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

Затем среда выполнения Функций отправляет следующий HTTP-запрос в пользовательский обработчик:

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

Примечание

Некоторые части полезных данных были удалены для наглядности.

handler.exe — это скомпилированная программа пользовательского обработчика на языке Go, которая запускает веб-сервер и реагирует на запросы вызова функций от узла Функций.

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

В этом примере пользовательский обработчик запускает веб-сервер для обработки событий HTTP и получения запросов через FUNCTIONS_CUSTOMHANDLER_PORT.

Несмотря на то что узел Функций получил исходный HTTP-запрос по адресу /api/order, узел вызывает пользовательский обработчик по имени функции (имени папки). В этом примере функция определена по пути /order. Узел отправляет HTTP-запрос пользовательского обработчика по пути /order.

По мере отправки запросов POST к этой функции данные триггера и метаданные функции содержатся в тексте HTTP-запроса. Доступ к исходному тексту HTTP-запроса можно получить в Data.req.Body полезных данных.

Ответ функции форматируется в пары "ключ/значение", где элемент Outputs содержит значение JSON, в котором ключи соответствуют выходным данным, определенным в файле function.json.

Это пример полезных данных, которые этот обработчик возвращает узлу Функций.

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

Если задать выходные данные message совпадающими с данными заказа, которые были получены из запроса, функция выводит эти данные заказа в настроенную очередь. Узел Функций также возвращает вызывающему ответ HTTP, настроенный в res.

Функция только HTTP

В отношении функций для триггеров HTTP без дополнительных привязок или выходных данных, вам, возможно, потребуется, чтобы обработчик работал непосредственно с HTTP-запросом и ответом, а не с полезными данными запроса и ответа пользовательского обработчика. Это поведение можно настроить в host.json с помощью параметра enableForwardingHttpRequest.

Важно!

Основным назначением функции пользовательских обработчиков является включение языков и сред выполнения, которые в настоящее время не имеют поддержки первого класса в Функциях Azure. Наряду с тем, что веб-приложения можно запускать с помощью пользовательских обработчиков, Функции Azure не являются стандартным обратным прокси-сервером. Некоторые функции, такие как потоковая передача ответов, HTTP/2 и WebSockets, недоступны. Некоторые компоненты HTTP-запроса, такие как определенные заголовки и маршруты, могут ограничиваться. Также ваше приложение может столкнуться с чрезмерным холодным запуском.

Чтобы устранить эти проблемы, попробуйте запускать веб-приложения в Службе приложений Azure.

В следующем примере показан способ настройки функции для триггеров HTTP без дополнительных привязок или выходных данных. Сценарий, реализованный в этом примере, поддерживает функцию с именем hello, которая принимает GET или POST.

Реализация

В папке с именем hello файл function.json содержит настройки функции для триггеров HTTP.

hello/function.json

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

Функция настроена на прием запросов как GET и POST, а результирующее значение предоставляется с помощью аргумента с именем res.

Файл host.json, находящийся в корне файловой системы приложения, настраивается для запуска handler.exe, и для параметра enableForwardingHttpRequest устанавливается значение true.

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

Если enableForwardingHttpRequest — это true, поведение функций только для протокола HTTP отличается от поведения пользовательских обработчиков по умолчанию следующим образом.

  • HTTP-запрос не содержит полезных данных запроса пользовательских обработчиков. Вместо этого узел Функций вызывает обработчик с копией исходного HTTP-запроса.
  • Узел Функций вызывает обработчик с тем же путем, что и исходный запрос, включая любые параметры строки запроса.
  • Узел Функций возвращает копию HTTP-ответа обработчика в качестве ответа на исходный запрос.

Ниже приведен POST-запрос к узлу Функций. Затем узел Функций отправляет копию запроса пользовательскому обработчику по тому же пути.

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

{
  "message": "Hello World!"
}

В файле handler.go реализуется веб-сервер и функция 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))
}

В этом примере пользовательский обработчик создает веб-сервер для обработки событий HTTP и получения запросов через FUNCTIONS_CUSTOMHANDLER_PORT.

Запросы GET обрабатываются возвратом строкового значения, а запросы POST имеют доступ к тексту запроса.

Маршрут для функции заказа /api/hello имеет то же значение, что и исходный запрос.

Примечание

FUNCTIONS_CUSTOMHANDLER_PORT — не является общедоступным портом, используемым для вызова функции. Этот порт используется узлом Функций для вызова пользовательского обработчика.

Развертывание

Пользовательский обработчик может быть развернут на каждом параметре размещения Функций Azure. Если обработчику требуются зависимости операционной системы или платформы (например, языковая среда выполнения), может потребоваться использование пользовательского контейнера.

При создании приложения-функции в Azure для пользовательских обработчиков мы рекомендуем в качестве стека выбрать .NET Core.

Чтобы развернуть приложение пользовательского обработчика с помощью средств Azure Functions Core Tools, выполните следующую команду.

func azure functionapp publish $functionAppName

Примечание

Убедитесь, что все файлы, необходимые для выполнения пользовательского обработчика, находятся в папке и включены в развертывание. Если пользовательский обработчик является двоичным исполняемым файлом или имеет зависящие от платформы зависимости, убедитесь, что эти файлы соответствуют целевой платформе развертывания.

Ограничения

  • Веб-сервер пользовательского обработчика должен запускаться в течение 60 секунд.

Примеры

Примеры реализации функций на различных языках см. в Репозитории примеров пользовательского обработчика GitHub.

Устранение неполадок и поддержка

Ведение журнала трассировки

Если не удается запустить процесс пользовательского обработчика или возникли проблемы при взаимодействии с узлом Функций, можно увеличить уровень ведения журнала приложения-функции до Trace, чтобы просмотреть дополнительные диагностические сообщения от узла.

Чтобы изменить уровень ведения журнала приложения-функции по умолчанию, настройте logLevel параметр в разделе logging файла host.json.

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

Узел Функции выводит дополнительные сообщения журнала, включая сведения, связанные с процессом пользовательского обработчика. Используйте журналы для изучения проблем, возникающих при запуске процесса пользовательского обработчика или вызова функций в пользовательском обработчике.

В локальной среде журналы выводятся на консоль.

В Azure запросите трассировки Application Insights, чтобы просмотреть сообщения журнала. Если приложение значительно увеличивает объем журналов, в Application Insights передается только подмножество сообщений журнала. Отключите выборку, чтобы убедиться в записи всех сообщений в журнал.

Тестирование пользовательского обработчика в изоляции

Приложения пользовательского обработчика — это процесс веб-сервера. В связи с этим может быть полезным запустить процесс на вызовах своих собственных и тестовых функций, передав макет HTTP-запросов с помощью такого средства, как cURL или Postman.

Эту стратегию можно использовать в конвейерах CI/CD для выполнения автоматических тестов в пользовательском обработчике.

Среда выполнения

Пользовательские обработчики запускаются в той же среде, что и типовое приложение Функций Azure. Протестируйте обработчик, чтобы убедиться, что среда содержит все зависимости, необходимые для выполнения. Для приложений, требующих дополнительных зависимостей, может потребоваться выполнить их с помощью пользовательского образа контейнера, размещенного в плане "Премиум" Функций Azure.

Техническая поддержка

Если вам нужна помощь в приложении-функции с пользовательскими обработчиками, вы можете отправить запрос по регулярным каналам поддержки. Однако из-за наличия большого количества различных языков программирования, используемых для создания приложений пользовательских обработчиков, поддержка ограничена.

Поддержка доступна, если в узле Функций имеются проблемы при запуске или взаимодействии с процессом настраиваемого обработчика. Для проблем, связанных с внутренней работой процесса пользовательского обработчика, например проблем с выбранным языком или платформой, наша группа поддержки не может предоставить помощь в этом контексте.

Дальнейшие действия

Приступая к созданию приложения "Функции Azure" на языке Go или Rust, ознакомьтесь с кратким руководством по пользовательским обработчикам.