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

使用命令行工具将 Azure Functions 连接到 Azure 存储

本文介绍如何将 Azure 存储队列与在前一篇快速入门中创建的函数和存储帐户相集成。 可以使用一个输出绑定来实现这种集成。该绑定可将 HTTP 请求中的数据写入队列中的消息。 除了在前一篇快速入门中提到的几美分费用以外,完成本文不会产生其他费用。 有关绑定的详细信息,请参阅 Azure Functions 触发器和绑定的概念

配置本地环境

在开始之前,必须完成文章快速入门:从命令行创建 Azure Functions 项目。 如果在该文章结束时清理了资源,请再次执行相应的步骤,以在 Azure 中重新创建函数应用和相关资源。

在开始之前,必须完成文章快速入门:从命令行创建 Azure Functions 项目。 如果在该文章结束时清理了资源,请再次执行相应的步骤,以在 Azure 中重新创建函数应用和相关资源。

在开始之前,必须完成文章快速入门:从命令行创建 Azure Functions 项目。 如果在该文章结束时清理了资源,请再次执行相应的步骤,以在 Azure 中重新创建函数应用和相关资源。

在开始之前,必须完成文章快速入门:从命令行创建 Azure Functions 项目。 如果在该文章结束时清理了资源,请再次执行相应的步骤,以在 Azure 中重新创建函数应用和相关资源。

在开始之前,必须完成文章快速入门:从命令行创建 Azure Functions 项目。 如果在该文章结束时清理了资源,请再次执行相应的步骤,以在 Azure 中重新创建函数应用和相关资源。

在开始之前,必须完成文章快速入门:从命令行创建 Azure Functions 项目。 如果在该文章结束时清理了资源,请再次执行相应的步骤,以在 Azure 中重新创建函数应用和相关资源。

检索 Azure 存储连接字符串

前面你已创建一个供函数应用使用的 Azure 存储帐户。 此帐户的连接字符串安全存储在 Azure 中的应用设置内。 将设置下载到 local.settings.json 文件中后,在本地运行函数时,可以在同一帐户中使用该连接写入存储队列。

  1. 从项目的根目录中运行以下命令,并将 <APP_NAME> 替换为上一步中所用的函数应用名称。 此命令将覆盖该文件中的任何现有值。

    func azure functionapp fetch-app-settings <APP_NAME>
    
  2. 打开 local.settings.json 文件,找到名为 AzureWebJobsStorage 的值,即存储帐户连接字符串。 在本文的其他部分,将使用名称 AzureWebJobsStorage 和该连接字符串。

重要

由于 local.settings.json 文件包含从 Azure 下载的机密,因此请始终从源代码管理中排除此文件。 连同本地函数项目一起创建的 .gitignore 文件默认会排除该文件。

注册绑定扩展

绑定(HTTP 和计时器触发器除外)将实现为扩展包。 在终端窗口中运行以下 dotnet add package 命令,将存储扩展包添加到项目中。

dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues --prerelease

现在,你可以将存储输出绑定添加到项目。

将输出绑定定义添加到函数

尽管一个函数只能有一个触发器,但它可以有多个输入和输出绑定,因此,你无需编写自定义集成代码就能连接到其他 Azure 服务和资源。

使用 Node.js v4 编程模型时,绑定属性直接在 ./src/functions/HttpExample.js 文件中定义。 在上一个快速入门中,你的文件已包含由 app.http 方法定义的 HTTP 绑定。

const { app } = require('@azure/functions');

app.http('httpTrigger', {
  methods: ['GET', 'POST'],
  authLevel: 'anonymous',
  handler: async (request, context) => {
    try {
      context.log(`Http function processed request for url "${request.url}"`);

      const name = request.query.get('name') || (await request.text());
      context.log(`Name: ${name}`);

      if (!name) {
        return { status: 404, body: 'Not Found' };
      }

      return { body: `Hello, ${name}!` };
    } catch (error) {
      context.log(`Error: ${error}`);
      return { status: 500, body: 'Internal Server Error' };
    }
  },
});

使用 Node.js v4 编程模型时,绑定属性直接在 ./src/functions/HttpExample.js 文件中定义。 在上一个快速入门中,你的文件已包含由 app.http 方法定义的 HTTP 绑定。

import {
  app,
  HttpRequest,
  HttpResponseInit,
  InvocationContext,
} from '@azure/functions';

export async function httpTrigger1(
  request: HttpRequest,
  context: InvocationContext,
): Promise<HttpResponseInit> {
  context.log(`Http function processed request for url "${request.url}"`);

  const name = request.query.get('name') || (await request.text()) || 'world';

  return { body: `Hello, ${name}!` };
}

app.http('httpTrigger1', {
  methods: ['GET', 'POST'],
  authLevel: 'anonymous',
  handler: httpTrigger1,
});

在函数文件夹中的 function.json 文件内声明这些绑定。 在前一篇快速入门中,HttpExample 文件夹中的 function.json 文件在 bindings 集合中包含两个绑定:

使用 Python v2 编程模型时,绑定属性直接在 function_app.py 文件中定义为修饰器。 在前面的快速入门中,function_app.py 文件已包含一个基于修饰器的绑定:

import azure.functions as func
import logging

app = func.FunctionApp()

@app.function_name(name="HttpTrigger1")
@app.route(route="hello", auth_level=func.AuthLevel.ANONYMOUS)

route 修饰器会将 HttpTrigger 和 HttpOutput 绑定添加到函数,这使函数能够在 http 请求命中指定的路由时触发。

要从此函数写入 Azure 存储队列,请将 queue_output 修饰器添加到函数代码:

@app.queue_output(arg_name="msg", queue_name="outqueue", connection="AzureWebJobsStorage")

在修饰器中,arg_name 标识代码中引用的绑定参数,queue_name 是绑定写入到的队列的名称,connection 是包含存储帐户连接字符串的应用程序设置的名称。 在快速入门中,将使用与函数应用相同的存储帐户,它位于 AzureWebJobsStorage 设置(来自 local.settings.json 文件)中。 如果 queue_name 不存在,首次使用绑定时,它会创建该属性。

"bindings": [
  {
    "authLevel": "function",
    "type": "httpTrigger",
    "direction": "in",
    "name": "Request",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "Response"
  }
]

若要写入到 Azure 存储队列,请执行以下操作:

  • extraOutputs 属性添加到绑定配置

    {
        methods: ['GET', 'POST'],
        extraOutputs: [sendToQueue], // add output binding to HTTP trigger
        authLevel: 'anonymous',
        handler: () => {}
    }
    
  • app.http 调用之上添加 output.storageQueue 函数

    const sendToQueue: StorageQueueOutput = output.storageQueue({
      queueName: 'outqueue',
      connection: 'AzureWebJobsStorage',
    });
    

集合中的第二个绑定名为 res。 此 http 绑定是用于写入 HTTP 响应的输出绑定 (out)。

若要从此函数写入 Azure 存储队列,请添加类型为 queue、名称为 msgout 绑定,如以下代码所示:

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

对于 queue 类型,必须在 queueName 中指定队列的名称,并在 connection 中提供 Azure 存储连接的名称(来自 local.settings.json 文件)。

在 C# 项目中,绑定被定义为函数方法上的绑定属性。 具体定义取决于应用是在进程内(C# 类库)还是在独立工作进程中运行。

打开 HttpExample.cs 项目文件并添加以下 类:

public class MultiResponse
{
    [QueueOutput("outqueue",Connection = "AzureWebJobsStorage")]
    public string[] Messages { get; set; }
    public HttpResponseData HttpResponse { get; set; }
}

MultiResponse 类允许写入到名为 outqueue 的存储队列和 HTTP 成功消息。 可以将多条消息发送到该队列,因为 QueueOutput 属性应用于一个字符串数组。

Connection 属性设置存储帐户的连接字符串。 在本例中,可以省略 Connection,因为你已使用默认存储帐户。

在 Java 项目中,绑定被定义为函数方法上的绑定注释。 然后根据这些注释自动生成 function.json 文件。

浏览到函数代码在 src/main/java 下的位置,打开 Function.java 项目文件,然后将以下参数添加到 run 方法定义:

@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") OutputBinding<String> msg

msg 参数是 OutputBinding<T> 类型,该类型表示字符串的集合。 当函数完成时,这些字符串作为消息写入到输出绑定。 在这种情况下,输出是名为的 outqueue 存储队列。 存储帐户的连接字符串由 connection 方法设置。 请传递包含存储帐户连接字符串的应用程序设置,而不是传递连接字符串本身。

run 方法定义现在必定如以下示例所示:

@FunctionName("HttpTrigger-Java")
public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)  
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") 
        OutputBinding<String> msg, final ExecutionContext context) {
    ...
}

有关绑定的详细信息,请参阅 Azure Functions 触发器和绑定的概念队列输出配置

添加使用输出绑定的代码

定义队列绑定后,可以更新函数,以接收 msg 输出参数并将消息写入队列。

更新 HttpExample\function_app.py 以匹配下面的代码,将 msg 参数添加到函数定义,并将 msg.set(name) 添加到 if name: 语句下:

import azure.functions as func
import logging

app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)

@app.route(route="HttpExample")
@app.queue_output(arg_name="msg", queue_name="outqueue", connection="AzureWebJobsStorage")
def HttpExample(req: func.HttpRequest, msg: func.Out [func.QueueMessage]) -> 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:
        msg.set(name)
        return func.HttpResponse(f"Hello, {name}. This HTTP triggered function executed successfully.")
    else:
        return func.HttpResponse(
             "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
             status_code=200
        )

msg 参数是 azure.functions.Out class 的实例。 set 方法将字符串消息写入队列。 在本例中,它是在 URL 查询字符串中传递给函数的 name

添加在 context.extraOutputs 上使用输出绑定对象来创建队列消息的代码。 在 return 语句之前添加此代码。

context.extraOutputs.set(sendToQueue, [msg]);

此时,你的函数应如下所示:

const { app, output } = require('@azure/functions');

const sendToQueue = output.storageQueue({
  queueName: 'outqueue',
  connection: 'AzureWebJobsStorage',
});

app.http('HttpExample', {
  methods: ['GET', 'POST'],
  authLevel: 'anonymous',
  extraOutputs: [sendToQueue],
  handler: async (request, context) => {
    try {
      context.log(`Http function processed request for url "${request.url}"`);

      const name = request.query.get('name') || (await request.text());
      context.log(`Name: ${name}`);

      if (name) {
        const msg = `Name passed to the function ${name}`;
        context.extraOutputs.set(sendToQueue, [msg]);
        return { body: msg };
      } else {
        context.log('Missing required data');
        return { status: 404, body: 'Missing required data' };
      }
    } catch (error) {
      context.log(`Error: ${error}`);
      return { status: 500, body: 'Internal Server Error' };
    }
  },
});

添加在 context.extraOutputs 上使用输出绑定对象来创建队列消息的代码。 在 return 语句之前添加此代码。

context.extraOutputs.set(sendToQueue, [msg]);

此时,你的函数应如下所示:

import {
  app,
  output,
  HttpRequest,
  HttpResponseInit,
  InvocationContext,
  StorageQueueOutput,
} from '@azure/functions';

const sendToQueue: StorageQueueOutput = output.storageQueue({
  queueName: 'outqueue',
  connection: 'AzureWebJobsStorage',
});

export async function HttpExample(
  request: HttpRequest,
  context: InvocationContext,
): Promise<HttpResponseInit> {
  try {
    context.log(`Http function processed request for url "${request.url}"`);

    const name = request.query.get('name') || (await request.text());
    context.log(`Name: ${name}`);

    if (name) {
      const msg = `Name passed to the function ${name}`;
      context.extraOutputs.set(sendToQueue, [msg]);
      return { body: msg };
    } else {
      context.log('Missing required data');
      return { status: 404, body: 'Missing required data' };
    }
  } catch (error) {
    context.log(`Error: ${error}`);
    return { status: 500, body: 'Internal Server Error' };
  }
}

app.http('HttpExample', {
  methods: ['GET', 'POST'],
  authLevel: 'anonymous',
  handler: HttpExample,
});

添加使用 Push-OutputBinding cmdlet 通过 msg 输出绑定将文本写入队列的代码。 在 if 语句中设置“正常”状态之前,请添加此代码。

$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg

此时,你的函数一定如下所示:

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
    # Write the $name value to the queue, 
    # which is the name passed to the function.
    $outputMsg = $name
    Push-OutputBinding -name msg -Value $outputMsg

    $status = [HttpStatusCode]::OK
    $body = "Hello $name"
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

将现有 HttpExample 类替换为以下代码:

    [Function("HttpExample")]
    public static MultiResponse Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
        FunctionContext executionContext)
    {
        var logger = executionContext.GetLogger("HttpExample");
        logger.LogInformation("C# HTTP trigger function processed a request.");

        var message = "Welcome to Azure Functions!";

        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
        response.WriteString(message);

        // Return a response to both HTTP trigger and storage output binding.
        return new MultiResponse()
        {
            // Write a single message.
            Messages = new string[] { message },
            HttpResponse = response
        };
    }
}

现在可以使用新的 msg 参数,从函数代码写入到输出绑定。 将以下代码行添加到成功响应之前,以便将 name 的值添加到 msg 输出绑定。

msg.setValue(name);

使用输出绑定时,无需使用 Azure 存储 SDK 代码进行身份验证、获取队列引用或写入数据。 Functions 运行时和队列输出绑定将为你执行这些任务。

run 方法现在一定如以下示例所示:

public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) 
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", 
        connection = "AzureWebJobsStorage") OutputBinding<String> msg, 
        final ExecutionContext context) {
    context.getLogger().info("Java HTTP trigger processed a request.");

    // Parse query parameter
    String query = request.getQueryParameters().get("name");
    String name = request.getBody().orElse(query);

    if (name == null) {
        return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
        .body("Please pass a name on the query string or in the request body").build();
    } else {
        // Write the name to the message queue. 
        msg.setValue(name);

        return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
    }
}

更新测试

由于原型还创建一组测试,因此需更新这些测试,以便处理 run 方法签名中的新 msg 参数。

在 src/test/java 下浏览到你的测试代码所在位置,打开 Function.java 项目文件,将 //Invoke 下的代码行替换为以下代码:

@SuppressWarnings("unchecked")
final OutputBinding<String> msg = (OutputBinding<String>)mock(OutputBinding.class);
final HttpResponseMessage ret = new Function().run(req, msg, context);

请注意,不需要编写任何用于身份验证、获取队列引用或写入数据的代码。 在 Azure Functions 运行时和队列输出绑定中可以方便地处理所有这些集成任务。

在本地运行函数

  1. 通过从 LocalFunctionProj 文件夹启动本地 Azure Functions 运行时主机来运行函数。

    func start
    

    在输出的末尾,必须要显示以下行:

    在本地运行函数时的终端窗口输出的屏幕截图。

    注意

    如果 HttpExample 未按如上所示出现,则可能是在项目的根文件夹外启动了主机。 在这种情况下,请按 Ctrl+C 停止主机,转至项目的根文件夹,然后重新运行上一命令。

  2. 将此输出中的 HTTP 函数的 URL 复制到浏览器,并追加查询字符串 ?name=<YOUR_NAME>,使完整 URL 类似于 http://localhost:7071/api/HttpExample?name=Functions。 浏览器应显示回显查询字符串值的响应消息。 当你发出请求时,启动项目时所在的终端还会显示日志输出。

  3. 完成后,按 Ctrl + C 并键入 y 以停止函数主机。

提示

在启动过程中,主机会下载并安装存储绑定扩展和其他 Microsoft 绑定扩展。 之所以安装这些扩展,是因为默认情况下,已在 host.json 文件中使用以下属性启用了绑定扩展:

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[1.*, 2.0.0)"
    }
}

如果遇到任何与绑定扩展相关的错误,请检查上述属性是否在 host.json 中存在。

查看 Azure 存储队列中的消息

可以在 Azure 门户Microsoft Azure 存储资源管理器中查看队列。 也可以按以下步骤中所述,在 Azure CLI 中查看队列:

  1. 打开函数项目的 local.setting.json 文件,并复制连接字符串值。 在终端或命令窗口中运行以下命令以创建名为 AZURE_STORAGE_CONNECTION_STRING 的环境变量,并粘贴特定的连接字符串来代替 <MY_CONNECTION_STRING>。 (创建此环境变量后,无需在使用 --connection-string 参数的每个后续命令中提供连接字符串。)

    export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
    
  2. (可选)使用 az storage queue list 命令查看帐户中的存储队列。 此命令的输出一定包含名为 outqueue 的队列,该队列是函数将其第一条消息写入该队列时创建的。

    az storage queue list --output tsv
    
  3. 使用 az storage message get 命令从该队列中读取消息,这应是之前测试函数时提供的值。 该命令读取再删除该队列中的第一条消息。

    echo `echo $(az storage message get --queue-name outqueue -o tsv --query '[].{Message:content}') | base64 --decode`
    

    由于消息正文是以 base64 编码格式存储的,因此,必须解码消息才能显示消息。 执行 az storage message get 后,该消息将从队列中删除。 如果 outqueue 中只有一条消息,则再次运行此命令时不会检索到消息,而是收到错误。

将项目重新部署到 Azure

在本地验证函数已将消息写入 Azure 存储队列后,接下来可以重新部署项目以更新 Azure 上运行的终结点。

LocalFunctionsProj 文件夹中,使用 func azure functionapp publish 命令重新部署项目(请将 <APP_NAME> 替换为你的应用的名称)。

func azure functionapp publish <APP_NAME>

在本地项目文件夹中,使用以下 Maven 命令重新发布项目:

mvn azure-functions:deploy

在 Azure 中验证

  1. 像在上一篇快速入门中一样,使用浏览器或 CURL 来测试重新部署的函数。

    将 publish 命令的输出中显示的完整“调用 URL”复制到浏览器的地址栏,并追加查询参数 &name=Functions。 浏览器应显示与本地运行函数时相同的输出。

  2. 按上一部分所述再次检查存储队列,验证它是否包含已写入到其中的新消息。

清理资源

完成后,请使用以下命令删除资源组及其包含的所有资源,以免产生额外的费用。

az group delete --name AzureFunctionsQuickstart-rg

后续步骤

现已更新 HTTP 触发的函数,使其将数据写入存储队列。 现在,可以详细了解如何使用 Core Tools 和 Azure CLI 通过命令行进行 Functions 开发: