Azure Functions HTTP 触发器和绑定

本文介绍如何在 Azure Functions 中使用 HTTP 触发器和输出绑定。

HTTP 触发器可进行自定义以响应 Webhook

此参考信息面向 Azure Functions 开发人员。 Azure Functions 的新手请从以下资源入手:

Tip

如果计划使用 HTTP 或 WebHook 绑定,请制定计划来避免因实例化 HttpClient 不当导致的端口耗尽现象。 有关详细信息,请参阅如何在 Azure Functions 中管理连接

包 - Functions 1.x

Microsoft.Azure.WebJobs.Extensions.Http NuGet 包 1.x 版本中提供了 HTTP 绑定。 azure-webjobs-sdk-extensions GitHub 存储库中提供了此包的源代码。

C# 类库

对于 C# 类库开发,自动在项目中安装该包。

Functions 2.x 中的绑定扩展

对于 Azure Functions 2.x 版中的本地开发,该包自动注册为绑定扩展

包 - Functions 2.x

Microsoft.Azure.WebJobs.Extensions.Http NuGet 包 3.x 版本中提供了 HTTP 绑定。 azure-webjobs-sdk-extensions GitHub 存储库中提供了此包的源代码。

C# 类库

对于 C# 类库开发,自动在项目中安装该包。

Functions 2.x 中的绑定扩展

对于 Azure Functions 2.x 版中的本地开发,该包自动注册为绑定扩展

触发器

借助 HTTP 触发器,可以使用 HTTP 请求调用函数。 可以使用 HTTP 触发器生成无服务器 API 和响应 Webhook。

默认情况下,在 Functions 1.x 中,HTTP 触发器返回“HTTP 200 正常”和空的正文;在 Functions 2.x 中返回“HTTP 204 无内容”和空的正文。 若要修改该响应,请配置 HTTP 输出绑定

触发器 - 示例

参阅语言特定的示例:

触发器 - C# 示例

以下示例显示一个在查询字符串或 HTTP 请求正文中查找 name 参数的 C# 函数。 请注意,返回值用于输出绑定,但不需要返回值属性。

[FunctionName("HttpTriggerCSharp")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, 
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    // parse query parameter
    string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

    // Get request body
    dynamic data = await req.Content.ReadAsAsync<object>();

    // Set name to query string or body data
    name = name ?? data?.name;

    return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}

触发器 - C# 脚本示例

以下示例演示 function.json 文件中的一个触发器绑定以及使用该绑定的 C# 脚本函数。 该函数在查询字符串或 HTTP 请求的正文中查找 name 参数。

function.json 文件如下所示:

{
    "disabled": false,
    "bindings": [
        {
            "authLevel": "function",
            "name": "req",
            "type": "httpTrigger",
            "direction": "in",
            "methods": [
                "get",
                "post"
            ]
        },
        {
            "name": "$return",
            "type": "http",
            "direction": "out"
        }
    ]
}

配置部分解释了这些属性。

下面是绑定到 HttpRequestMessage 的 C# 脚本代码:

using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log)
{
    log.LogInformation($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");

    // parse query parameter
    string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

    // Get request body
    dynamic data = await req.Content.ReadAsAsync<object>();

    // Set name to query string or body data
    name = name ?? data?.name;

    return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}

可以绑定到自定义对象而不是 HttpRequestMessage。 此对象从请求正文进行创建,并分析为 JSON。 同样,可以将类型传递给 HTTP 响应输出绑定,并作为响应正文随 200 状态代码一同返回。

using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public static string Run(CustomObject req, ILogger log)
{
    return "Hello " + req?.name;
}

public class CustomObject {
     public string name {get; set;}
}

触发器 - F# 示例

以下示例演示 function.json 文件中的一个触发器绑定以及使用该绑定的 F# 函数。 该函数在查询字符串或 HTTP 请求的正文中查找 name 参数。

function.json 文件如下所示:

{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "res",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

配置部分解释了这些属性。

F# 代码如下所示:

open System.Net
open System.Net.Http
open FSharp.Interop.Dynamic

let Run(req: HttpRequestMessage) =
    async {
        let q =
            req.GetQueryNameValuePairs()
                |> Seq.tryFind (fun kv -> kv.Key = "name")
        match q with
        | Some kv ->
            return req.CreateResponse(HttpStatusCode.OK, "Hello " + kv.Value)
        | None ->
            let! data = Async.AwaitTask(req.Content.ReadAsAsync<obj>())
            try
                return req.CreateResponse(HttpStatusCode.OK, "Hello " + data?name)
            with e ->
                return req.CreateErrorResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
    } |> Async.StartAsTask

需要一个使用 NuGet 引用 FSharp.Interop.DynamicDynamitey 程序集的 project.json 文件,如以下示例中所示:

{
  "frameworks": {
    "net46": {
      "dependencies": {
        "Dynamitey": "1.0.2",
        "FSharp.Interop.Dynamic": "3.0.0"
      }
    }
  }
}

触发器 - JavaScript 示例

以下示例演示 function.json 文件中的一个触发器绑定以及使用该绑定的 JavaScript 函数。 该函数在查询字符串或 HTTP 请求的正文中查找 name 参数。

function.json 文件如下所示:

{
    "disabled": false,    
    "bindings": [
        {
            "authLevel": "function",
            "type": "httpTrigger",
            "direction": "in",
            "name": "req"
        },
        {
            "type": "http",
            "direction": "out",
            "name": "res"
        }
    ]
}

配置部分解释了这些属性。

JavaScript 代码如下所示:

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    if (req.query.name || (req.body && req.body.name)) {
        context.res = {
            // status defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
    context.done();
};

触发器 - Python 示例

以下示例演示 function.json 文件中的一个触发器绑定以及使用该绑定的 Python 函数。 该函数在查询字符串或 HTTP 请求的正文中查找 name 参数。

function.json 文件如下所示:

{
    "scriptFile": "__init__.py",
    "disabled": false,    
    "bindings": [
        {
            "authLevel": "function",
            "type": "httpTrigger",
            "direction": "in",
            "name": "req"
        },
        {
            "type": "http",
            "direction": "out",
            "name": "res"
        }
    ]
}

配置部分解释了这些属性。

下面是 Python 代码:

import logging
import azure.functions as func

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    name = req.params.get('name')
    if not name:
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            name = req_body.get('name')

    if name:
        return func.HttpResponse(f"Hello {name}!")
    else:
        return func.HttpResponse(
            "Please pass a name on the query string or in the request body",
            status_code=400
        )

触发器 - Java 示例

以下示例演示 function.json 文件中的一个触发器绑定以及使用该绑定的 Java 函数。 此函数返回 HTTP 状态代码为 200 的响应和请求正文,触发性的请求正文带有“Hello, ”问候语前缀。

function.json 文件如下所示:

{
    "disabled": false,    
    "bindings": [
        {
            "authLevel": "anonymous",
            "type": "httpTrigger",
            "direction": "in",
            "name": "req"
        },
        {
            "type": "http",
            "direction": "out",
            "name": "res"
        }
    ]
}

下面是 Java 代码:

@FunctionName("hello")
public HttpResponseMessage<String> hello(@HttpTrigger(name = "req", methods = {"post"}, authLevel = AuthorizationLevel.ANONYMOUS), Optional<String> request,
                        final ExecutionContext context)
    {
        // default HTTP 200 response code
        return String.format("Hello, %s!", request);
    }
}

触发器 - 特性

C# 类库中,使用 HttpTrigger 特性。

可以在特性构造函数参数中设置授权级别和允许的 HTTP 方法,Webhook 类型和路由模板有相应的属性。 有关这些设置的详细信息,请参阅触发器 - 配置。 下面是某个方法签名中的 HttpTrigger 特性:

[FunctionName("HttpTriggerCSharp")]
public static HttpResponseMessage Run(
    [HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestMessage req)
{
    ...
}

有关完整示例,请参阅触发器 - C# 示例

触发器 - 配置

下表解释了在 function.json 文件和 HttpTrigger 特性中设置的绑定配置属性。

function.json 属性 Attribute 属性 说明
类型 不适用 必需 - 必须设置为 httpTrigger
direction 不适用 必需 - 必须设置为 in
name 不适用 必需 - 在请求或请求正文的函数代码中使用的变量名称。
authLevel AuthLevel 确定请求中需要提供的密钥(如果有),以便调用此函数。 授权级别可以是以下值之一:
  • anonymous—无需 API 密钥。
  • function—特定于函数的 API 密钥是必需的。 如果未提供任何值,该值为默认值。
  • admin—无需主密钥。
有关详细信息,请参阅有关授权密钥的部分。
methods 方法 HTTP 方法的数组,该函数将响应此方法。 如果未指定,该函数将响应所有 HTTP 方法。 参阅自定义 HTTP 终结点
route Route 定义路由模板,控制函数将响应的请求 URL。 如果未提供任何值,则默认值为 <functionname>。 有关详细信息,请参阅自定义 HTTP 终结点
webHookType WebHookType 仅支持 1.x 版运行时。

将 HTTP 触发器配置为充当指定提供程序的 webhook 接收器。 如果未设置此属性,请不要设置 methods 属性。 Webhook 类型可以是以下值之一:
  • genericJson—不包含特定提供程序逻辑的常规用途 webhook 终结点。 此设置会将请求限制为仅请求使用 HTTP POST 以及内容类型为 application/json
  • github—该函数响应 GitHub Webhook。 不要对 GitHub Webhook 使用 authLevel 属性。 有关详细信息,请参阅本文后面的“GitHub Webhook”部分。
  • slack—该函数响应 Slack Webhook。 不要对 Slack Webhook 使用 authLevel 属性。 有关详细信息,请参阅本文后面的“Slack Webhook”部分。

触发器 - 用法

对于 C# 和 F # 函数,可以将触发器输入的类型声明为 HttpRequestMessage 或自定义类型。 如果选择 HttpRequestMessage,会获得对请求对象的完全访问权限。 对于自定义类型,运行时会尝试分析 JSON 请求正文,以设置对象属性。

对于 JavaScript 函数,Functions 运行时提供请求正文而不是请求对象。 有关详细信息,请参阅 JavaScript 触发器示例

自定义 HTTP 终结点

默认情况下,创建 HTTP 触发器的函数时,可通过以下格式的路由对该函数进行寻址:

http://<yourapp>.chinacloudsites.cn/api/<funcname>

在 HTTP 触发器的输入绑定中,可以使用可选 route 属性自定义此路由。 例如,以下 function.json 文件定义了 HTTP 触发器的 route 属性:

{
    "bindings": [
    {
        "type": "httpTrigger",
        "name": "req",
        "direction": "in",
        "methods": [ "get" ],
        "route": "products/{category:alpha}/{id:int?}"
    },
    {
        "type": "http",
        "name": "res",
        "direction": "out"
    }
    ]
}

通过此配置,现在可以通过以下路由对该函数进行寻址,而不是通过原始路由寻址。

http://<yourapp>.chinacloudsites.cn/api/products/electronics/357

这使得函数代码可以支持地址中的两个参数:“category”和“id”。可以将任何 Web API 路由约束与参数配合使用。 以下 C# 函数代码使用了这两个参数。

public static Task<HttpResponseMessage> Run(HttpRequestMessage req, string category, int? id,
                                                ILogger log)
{
    if (id == null)
        return  req.CreateResponse(HttpStatusCode.OK, $"All {category} items were requested.");
    else
        return  req.CreateResponse(HttpStatusCode.OK, $"{category} item with id = {id} has been requested.");
}

以下是使用相同路由参数的 Node.js 函数代码。

module.exports = function (context, req) {

    var category = context.bindingData.category;
    var id = context.bindingData.id;

    if (!id) {
        context.res = {
            // status defaults to 200 */
            body: "All " + category + " items were requested."
        };
    }
    else {
        context.res = {
            // status defaults to 200 */
            body: category + " item with id = " + id + " was requested."
        };
    }

    context.done();
}

默认情况下,所有函数路由的前缀均为 api。 还可以使用 host.json 文件中的 http.routePrefix 属性自定义或删除前缀。 以下示例通过将空字符串用于 host.json 文件中的前缀删除 api 路由前缀。

{
    "http": {
    "routePrefix": ""
    }
}

使用客户端标识

如果函数应用使用应用服务身份验证/授权,则可通过代码查看有关已验证身份的客户端的信息。 此信息以平台注入的请求标头的形式提供。

还可从绑定数据中读取此信息。 此功能仅可用于 Functions 2.x 运行时, 而且它目前仅可用于 .NET 语言。

在 .NET 语言中,此信息以 ClaimsPrincipal 的形式提供。 ClaimsPrincipal 作为请求上下文的一部分提供,如以下示例中所示:

using System.Net;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

public static IActionResult Run(HttpRequest req, ILogger log)
{
    ClaimsPrincipal identities = req.HttpContext.User;
    // ...
    return new OkResult();
}

或者,可直接将 ClaimsPrincipal 作为其他参数包含在函数签名中:

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Newtonsoft.Json.Linq;

public static void Run(JObject input, ClaimsPrincipal principal, ILogger log)
{
    // ...
    return;
}

授权密钥

Functions 允许使用密钥来增大开发期间访问 HTTP 函数终结点的难度。 标准 HTTP 触发器可能会要求在请求中出现此类 API 密钥。

Important

虽然密钥可以帮助你在开发过程中对 HTTP 终结点进行模糊处理,它们不应作为一种方法来保护生产环境中的 HTTP 触发器。 若要了解详细信息,请参阅在生产环境中保护 HTTP 终结点

Note

在 Functions 1.x 运行时中,Webhook 提供程序可以使用密钥以多种方式对请求授权,具体取决于提供程序支持何种方式。 Webhook 和密钥对此进行了说明。 2.x 版运行时不包括对 Webhook 提供程序的内置支持。

有两种类型的密钥:

  • 主机密钥:由 Function App 中的所有函数共享这些密钥。 这些密钥用作 API 密钥时,可以访问 Function App 中的任何函数。
  • 函数密钥:这些密钥仅适用于在其下定义它们的特定函数。 这些密钥用作 API 密钥时,只允许访问该函数。

命名每个密钥方便引用,并且在函数和主机级别存在名为“default”的默认密钥。 函数密钥优先于主机密钥。 如果为两个密钥定义的名称相同,则使用函数密钥。

每个函数应用还有一个特殊的主密钥。 此密钥是名为 _master 的宿主密钥,提供对运行时 API 的管理访问。 无法撤消此密钥。 设置 admin 的授权级别时,请求必须使用主密钥;使用其他任何密钥会导致授权失败。

Caution

由于主密钥在函数应用中授予提升的权限,不应与第三方共享此密钥或在本机客户端应用程序中分发此密钥。 选择管理员授权级别时,请务必审慎行事。

获取密钥

密钥作为 Function App 的一部分存储在 Azure 中,并进行了静态加密。 若要查看密钥,请创建新的密钥或将密钥滚动到新值,在 Azure 门户中导航到某个 HTTP 触发的函数,然后选择“管理”。

在门户中管理函数密钥。

没有任何受支持的 API 能够以编程方式获取函数密钥。

API 密钥的授权

大多数 HTTP 触发器模板要求在请求中提供 API 密钥。 因此,HTTP 请求通常类似于以下 URL:

https://<yourapp>.chinacloudsites.cn/api/<function>?code=<ApiKey>

可将该密钥包含在名为 code 的查询字符串变量中,如上所示。 也可以将它包含在 x-functions-key HTTP 标头中。 密钥的值可以为任意为函数定义的函数密钥,也可以为任意主机密钥。

可以允许匿名请求,它不需要密钥。 可能还需要使用主密钥。 可使用绑定 JSON 中的 authLevel 属性更改默认授权级别。 有关详细信息,请参阅触发器 - 配置

Note

在本地运行函数时,不管指定的身份验证级别设置为何,都会禁用授权。 发布到 Azure 后,将强制执行触发器中的 authLevel 设置。

在生产环境中保护 HTTP 终结点

若要全面保护生产环境中的函数终结点,应考虑实现以下函数应用级别的安全选项之一:

  • 打开函数应用的“应用服务身份验证/授权”。 应用服务平台允许使用 Azure Active Directory (AAD) 和多个第三方标识提供者对客户端进行身份验证。 可以使用此函数来实现函数的自定义授权规则,并且可以从函数代码处理用户信息。 若要了解详细信息,请参阅 Azure 应用服务中的身份验证和授权以及使用客户端标识

  • 使用 Azure API 管理 (APIM) 对请求进行身份验证。 APIM 针对传入的请求提供各种 API 安全选项。 若要了解详细信息,请参阅 API 管理身份验证策略。 有了 APIM,可以配置函数应用以接受仅来自 APIM 实例 IP 地址的请求。 有关详细信息,请参阅 IP 地址限制

使用其中的某个函数应用级安全方法时,应将 HTTP 触发的函数身份验证级别设置为 anonymous

Webhook

Note

Webhook 模式仅适用于 1.x 版 Functions 运行时。 进行此更改是为了提高 2.x 版中 HTTP 触发器的性能。

在 1.x 版中,Webhook 模板为 Webhook 有效负载提供了额外的验证。 在 2.x 版中,基本 HTTP 触发器仍正常工作,且是针对 Webhook 的推荐方法。

GitHub Webhook

要响应 GitHub webhook,首先请创建包含 HTTP 触发器的函数,并将 webHookType 属性设置为 github。 然后将其 URL 和 API 密钥复制到 GitHub 存储库的“添加 Webhook”页。

Slack Webhook

Slack webhook 为用户生成令牌,而非让用户指定它,所以必须使用 Slack 中的令牌配置特定于函数的密钥。 请参阅授权密钥

Webhook 和密钥

Webhook 授权由属于 HTTP 触发器的 webhook 接收器组件处理,其机制因 webhook 类型而异。 每种机制都依赖于一个密钥。 默认情况下,使用名为“default”的函数密钥。 要使用其他密钥,请将 webhook 提供程序配置为使用以下方式之一的请求发送密钥名称:

  • 查询字符串:提供程序通过 clientid 查询字符串参数(例如,https://<yourapp>.chinacloudsites.cn/api/<funcname>?clientid=<keyname>)传递密钥名称。
  • 请求标头:提供程序通过 x-functions-clientid 标头传递密钥名称。

触发器 - 限制

HTTP 请求长度限制为 100 MB(104,857,600 字节),并且 URL 长度限制为 4 KB(4,096 字节)。 这些限制由运行时的 Web.config 文件httpRuntime 元素指定。

如果使用 HTTP 触发器的函数未在大约 2.5 分钟内完成,网关将超时并返回 HTTP 502 错误。 该函数将继续运行,但将无法返回 HTTP 响应。 对于长时间运行的函数,我们建议你遵循异步模式,并返回可以 ping 通请求状态的位置。

触发器 - host.json 属性

host.json 文件包含控制 HTTP 触发器行为的设置。

{
    "http": {
        "routePrefix": "api",
        "maxOutstandingRequests": 200,
        "maxConcurrentRequests": 100,
        "dynamicThrottlesEnabled": true
    }
}
属性 默认 说明
routePrefix api 应用到所有路由的路由前缀。 使用空字符串可删除默认前缀。
maxOutstandingRequests 200* 在任意给定时间搁置的未完成请求数上限。 此限制包括已排队但尚未开始执行的请求,以及正在执行的所有请求。 超出此限制的任何传入请求将被拒绝,并返回 429“太忙”响应。 允许调用方使用基于时间的重试策略,还可帮助控制最大请求延迟。 此设置仅控制脚本宿主执行路径中发生的排队。 其他队列(例如 ASP.NET 请求队列)仍有效,不受此设置的影响。 *版本 1.x 的默认值是无限制的。 专用计划中版本 2.x 的默认值是无限制的。
maxConcurrentRequests 100* 要并行执行的 http 函数数目上限。 这样,可以控制并发性,从而帮助管理资源利用率。 例如,某个 http 函数可能使用了大量系统资源(内存/CPU/插槽),从而在并发性过高时导致问题。 或者,某个函数向第三方服务发出出站请求,则可能需要限制这些调用的速率。 在这种情况下,应用限制可能有帮助。 *版本 1.x 的默认值是无限制的。 专用计划中版本 2.x 的默认值是无限制的。
dynamicThrottlesEnabled true* 启用时,将为此设置将导致请求处理管道,以定期检查系统性能计数器类似连接/线程/进程/内存/CPU 等,并通过内置的高阈值 (80%),如果有任何这些计数器请求拒绝与 429“太忙”响应,直至恢复到正常水平的计数器。 *版本 1.x 的默认值是 false。 专用计划中版本 2.x 的默认值为 false。

输出

通过 HTTP 输出绑定响应 HTTP 请求发送者。 此绑定需要使用 HTTP 触发器,利用此绑定,可以自定义与触发器请求相关联的响应。 如果未提供 HTTP 输出绑定,在 Functions 1.x 中,HTTP 触发器返回“HTTP 200 正常”和空的正文;在 Functions 2.x 中返回“HTTP 204 无内容”和空的正文。

输出 - 配置

下表解释了在 function.json 文件中设置的绑定配置属性。 在 C# 类库中,没有与这些 function.json 属性对应的特性。

属性 说明
type 必须设置为 http
direction 必须设置为 out
name 在响应的函数代码中使用的变量名称,或者 $return 以使用返回值。

输出 - 用法

若要发送 HTTP 响应,请使用语言标准响应模式。 在 C# 或 C# 脚本中,使函数返回类型为 HttpResponseMessageTask<HttpResponseMessage>。 在 C# 中,不需要返回值属性。

有关示例响应,请参阅触发器示例

后续步骤

详细了解 Azure Functions 触发器和绑定