你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

Azure Functions 自定义处理程序

Azure Functions 使用特定于语言的处理程序执行应用代码。 这些特定于语言的处理程序默认允许 Functions 支持 大多数关键语言 。 但是,可能需要以其他语言或包运行代码。

自定义处理程序是可接收 Azure Functions 主机进程事件的轻型 Web 服务器。 可以使用自定义处理程序部署到 Azure Functions 任何支持 HTTP 基元的代码项目。

自定义处理程序最适合用于以下场合:

  • 以当前未提供的现成语言(如 Go 或 Rust)实现函数应用程序。
  • 使用当前默认不提供的运行时(如 Deno)实现函数应用。
  • 使用标准 MCP SDK 生成的服务器部署到 Azure Functions。

对于自定义处理程序,可以通过扩展绑定来使用触发器以及输入和输出绑定

通过 Go 和 Rust 中的快速入门开始使用 Azure Functions 自定义处理程序。

概述

下图显示了 Functions 主机与作为自定义处理程序实现的 Web 服务器之间的关系。

Azure Functions 自定义处理程序概述

  1. 每个事件都会触发发送到 Functions 主机的请求。 事件是 Azure Functions 支持的任何触发器。
  2. 然后,Functions 主机向 Web 服务器发出请求有效负载。 有效负载包含该函数的触发器、输入绑定数据及其他元数据。
  3. Web 服务器执行单个函数,并向 Functions 主机返回响应有效负载
  4. Functions 主机将响应中的数据传递至函数的输出绑定以进行处理。

作为自定义处理程序实现的 Azure Functions 应用必须按照若干约定配置 host.jsonlocal.settings.jsonfunction.json 文件。

部署自承载 MCP 服务器

自定义处理程序还允许你在 Azure Functions 中使用官方 MCP SDK 来托管生成的 MCP 服务器。 自定义处理程序提供在 Azure 中托管 MCP 服务器的简单简化体验。 有关详细信息,请参阅 Azure Functions 上的自承载远程 MCP 服务器

注意

使用官方 MCP SDK 创建的 MCP 服务器可以由 Azure Functions 主机托管,其功能目前为预览版。

应用程序结构

若要实现自定义处理程序,应用程序需要以下方面:

  • 位于应用根目录的 host.json 文件
  • 位于应用根目录的 local.settings.json 文件
  • 每个函数对应一个 function.json 文件(位于与函数名称匹配的文件夹中)
  • 运行 Web 服务器的命令、脚本或可执行文件

下图显示了在名为“MyQueueFunction”的函数和名为“handler.exe”的自定义处理程序可执行文件的文件系统中,这些文件的样子。

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

配置

通过 host.jsonlocal.settings.json 文件配置应用程序。

host.json

host.json 通过指向可以处理 HTTP 事件的 Web 服务器来指示 Functions 主机在何处发送请求。

通过配置 host.json 文件来定义自定义处理程序,其中详细介绍了如何通过 customHandler 节运行 Web 服务器。

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

customHandler 节指向 defaultExecutablePath 所定义的目标。 执行目标可以是实现 Web 服务器的命令、可执行文件或文件。

使用 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 设置为

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

函数元数据

使用自定义处理程序时, function.json 内容与在任何其他上下文中定义函数时相同。 唯一的要求是必须将 function.json 文件放置在名为与函数名称匹配的文件夹中。

以下 function.json 配置了一个包含队列触发器和队列输出绑定的函数。 由于它位于名为 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 结构包含以下两个成员:DataMetadata

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 数组定义的响应值。

例如,如果函数配置了名为“myQueueOutput”的队列输出绑定,则 Outputs 包含名为 myQueueOutput 的键,自定义处理程序会将该键设置为发送到队列的消息。
Logs 数组 函数调用日志中显示的消息。

在 Azure 中运行时,消息会显示在 Application Insights 中。
ReturnValue 字符串 当输出在 $return 文件中配置为 时,用于提供响应。

下表显示了响应有效负载的示例。

{
  "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 的可执行文件(在 Linux 或 macOS 中为 handler)。

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

这是发送到 Functions 运行时的 HTTP 请求。

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

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

Functions 运行时将以下 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 自定义处理程序程序,它运行 Web 服务器并响应来自 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))
}

在此示例中,自定义处理程序通过运行 Web 服务器来处理 HTTP 事件,并通过 FUNCTIONS_CUSTOMHANDLER_PORT 侦听请求。

尽管 Functions 主机在 /api/order 接收原始 HTTP 请求,但它会通过函数名称(即其文件夹名称)调用自定义处理程序。 在此示例中,该函数在 /order 路径下定义。 主机在 /order 路径向自定义处理程序发送 HTTP 请求。

向此函数发送 POST 请求时,触发器数据和函数元数据可通过 HTTP 请求正文获取。 可在有效负载的 Data.req.Body 中访问原始 HTTP 请求正文。

函数的响应采用键/值对格式,其中 Outputs 成员包含一个 JSON 值,该值中的键与 function.json 文件中定义的输出相匹配。

以下是此处理程序返回到 Functions 主机的示例有效负载。

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

通过将 message 输出设置为与请求中的订单数据相等,该函数会将该订单数据输出到已配置的队列。 Functions 主机还会将 res 中配置的 HTTP 响应返回给调用方。

仅限 HTTP 的函数

对于没有附加绑定或输出的 HTTP 触发函数,你可能希望处理程序直接使用 HTTP 请求和响应,而不是自定义处理程序 请求响应 有效负载。 可以使用支持响应流式处理的设置在 host.jsonenableProxyingHttpRequest 中配置此行为。

重要说明

自定义处理程序功能的主要用途是启用当前在 Azure Functions 上没有一流支持的语言和运行时。 虽然可以使用自定义处理程序运行 Web 应用程序,但 Azure Functions 不是标准反向代理。 HTTP 请求的某些组件(如某些标头和路由)可能会受到限制。 您的应用程序可能还会遇到过多的冷启动。

若要解决这些情况,请考虑在 Azure 应用服务上运行 Web 应用。

以下示例演示如何配置一个无其他绑定或输出的 HTTP 触发函数。 此示例中实现的方案包含一个名为 hello 的函数,可接受 GETPOST

实现

在名为 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
  }
}

下面是对 Functions 主机的 POST 请求。 然后,Functions 主机将请求发送到自定义处理程序。

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

{
  "message": "Hello World!"
}

handler.go 文件实现 Web 服务器和 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))
}

在此示例中,自定义处理程序创建一个 Web 服务器来处理 HTTP 事件,并通过 FUNCTIONS_CUSTOMHANDLER_PORT 侦听请求。

GET 请求通过返回字符串处理,POST 请求可访问请求正文。

此处订单函数的路由为 /api/hello,与原始请求相同。

注意

FUNCTIONS_CUSTOMHANDLER_PORT不是用于调用函数的面向公众的端口。 Functions 主机使用此端口调用自定义处理程序。

正在部署

可以将自定义处理程序部署到每个 Azure Functions 托管选项。 如果处理程序需要作系统或平台依赖项(例如语言运行时),则可能需要使用 自定义容器

在 Azure 中为自定义处理程序创建函数应用时,选择 .NET Core 作为堆栈。

若要使用 Azure Functions Core Tools 部署自定义处理程序应用,请运行以下命令。

func azure functionapp publish $functionAppName

注意

请确保运行自定义处理程序所需的所有文件均位于该文件夹中,并包含在部署内容中。 如果自定义处理程序是二进制可执行文件或具有特定于平台的依赖项,请确保这些文件与目标部署平台匹配。

限制

  • 自定义处理程序 Web 服务器需要在 60 秒内启动。

示例

有关如何以各种不同语言实现函数的示例,请参阅 自定义处理程序示例 GitHub 存储库

故障排除和支持

跟踪日志记录

如果自定义处理程序进程无法启动,或与 Functions 主机通信有问题,请提高函数应用的日志级别至 Trace,以查看来自主机的更多诊断信息。

若要更改函数应用的默认日志级别,请在 logLevellogging 部分中配置 设置。

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

Functions 主机输出额外的日志消息,包括与自定义处理程序进程相关的信息。 使用日志调查启动自定义处理程序进程或调用自定义处理程序中的函数时遇到的问题。

日志在本地输出到控制台。

在 Azure 中,查询 Application Insights 跟踪以查看日志消息。 如果应用生成大量日志,则只有一部分日志消息会发送到 Application Insights。 禁用采样以确保记录所有消息。

以隔离方式测试自定义处理程序

自定义处理程序应用是 Web 服务器进程,因此可以单独启动它们,并通过发送模拟 HTTP 请求来测试函数调用,这可能会很有帮助。 若要发送具有有效负载的 HTTP 请求,请确保选择一个保证数据安全的工具。 有关详细信息,请参阅 HTTP 测试工具

还可以在 CI/CD 管道中使用此策略,对自定义处理程序运行自动化测试。

执行环境

自定义处理程序与典型的 Azure Functions 应用在同一环境中运行。 测试处理程序,确保环境包含其运行所需的所有依赖项。 对于需要其他依赖项的应用,可能需要使用 Azure Functions Premium 计划上托管的自定义容器映像来运行它们。

获取支持

如果需要有关包含自定义处理程序的函数应用的帮助,可以通过常规支持渠道提交请求。 由于用于构建自定义处理程序应用程序的语言种类繁多,支持是有限的。

如果 Functions 主机在启动自定义处理程序进程或与该进程通信时遇到问题,可获取相关支持。 对于特定于自定义处理程序过程内部工作(如所选语言或框架问题)的问题,我们的支持团队无法在此上下文中提供帮助。

后续步骤

通过自定义处理程序快速入门,开始使用 Go 或 Rust 构建 Azure Functions 应用。