Поделиться через


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

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

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

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

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

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

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

Обзор

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

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

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

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

Развертывание локальных серверов MCP

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

Примечание.

Возможность использования Azure Functions для размещения серверов MCP, которые вы создаете с помощью официальных SDK MCP, в настоящее время находится в стадии предварительного просмотра.

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

Для реализации пользовательского обработчика приложению требуются следующие аспекты:

  • Файл 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.json из системы контроля версий. В 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"
    }
  }
}

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

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

Ключ полезных данных Тип данных Замечания
Outputs объект Содержит значения ответа, определенные в виде массива 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": "[4.*, 5.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.

Несмотря на то, что узел Functions получает исходный 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-запросом и HTTP-ответом вместо тела запроса обработчика и тела ответа. Это поведение можно настроить в host.json с помощью enableProxyingHttpRequest параметра, который поддерживает потоковую передачу ответов.

Внимание

Основной целью функции пользовательских обработчиков является обеспечение поддержки языков и сред выполнения, для которых в данный момент отсутствует первоклассная поддержка в Функциях Azure. Хотя вы можете запускать веб-приложения с помощью пользовательских обработчиков, Azure Functions не являются стандартным обратным прокси-сервером. Некоторые компоненты 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"
    }
  ]
}

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

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

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

Ниже приведен запрос 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, выполните следующую команду.

func azure functionapp publish $functionAppName

Примечание.

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

Ограничения

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

Примеры

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

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

Просмотр журналов трассировки Java в Application Insights

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

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

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

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

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

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

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

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

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

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

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

Поддержка

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

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

Следующие шаги

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