使用命令行工具将 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 文件中后,在本地运行函数时,可以在同一帐户中使用该连接写入存储队列。
从项目的根目录中运行以下命令,并将
<APP_NAME>
替换为上一步中所用的函数应用名称。 此命令将覆盖该文件中的任何现有值。func azure functionapp fetch-app-settings <APP_NAME>
打开 local.settings.json 文件,找到名为
AzureWebJobsStorage
的值,即存储帐户连接字符串。 在本文的其他部分,将使用名称AzureWebJobsStorage
和该连接字符串。
重要
由于 local.settings.json 文件包含从 Azure 下载的机密,因此请始终从源代码管理中排除此文件。 连同本地函数项目一起创建的 .gitignore 文件默认会排除该文件。
注册绑定扩展
绑定(HTTP 和计时器触发器除外)将实现为扩展包。 在终端窗口中运行以下 dotnet add package 命令,将存储扩展包添加到项目中。
现在,你可以将存储输出绑定添加到项目。
将输出绑定定义添加到函数
尽管一个函数只能有一个触发器,但它可以有多个输入和输出绑定,因此,你无需编写自定义集成代码就能连接到其他 Azure 服务和资源。
在函数文件夹中的 function.json 文件内声明这些绑定。 在前一篇快速入门中,HttpExample 文件夹中的 function.json 文件在 bindings
集合中包含两个绑定:
声明绑定属性的方式取决于 Python 编程模型。
在函数文件夹中的 function.json 文件内声明这些绑定。 在前一篇快速入门中,HttpExample 文件夹中的 function.json 文件在 bindings
集合中包含两个绑定:
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
每个绑定至少有一个类型、一个方向和一个名称。 在以上示例中,第一个绑定的类型为 httpTrigger
,方向为 in
。 对于 in
方向,name
指定在触发器调用函数时,要发送到该函数的输入参数的名称。
集合中第二个绑定的类型为 http
,方向为 out
,在本例中,$return
的特殊 name
指示此绑定使用函数的返回值,而不是提供输入参数。
若要从此函数写入 Azure 存储队列,请添加类型为 queue
、名称为 msg
的 out
绑定,如以下代码所示:
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
},
{
"type": "queue",
"direction": "out",
"name": "msg",
"queueName": "outqueue",
"connection": "AzureWebJobsStorage"
}
]
在这种情况下,msg
将作为输出参数提供给函数。 对于 queue
类型,还必须在 queueName
中指定队列的名称,并在 connection
中提供 Azure 存储连接的名称(来自 local.settings.json 文件)。
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "Request",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "Response"
}
]
集合中的第二个绑定名为 res
。 此 http
绑定是用于写入 HTTP 响应的输出绑定 (out
)。
若要从此函数写入 Azure 存储队列,请添加类型为 queue
、名称为 msg
的 out
绑定,如以下代码所示:
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "queue",
"direction": "out",
"name": "msg",
"queueName": "outqueue",
"connection": "AzureWebJobsStorage"
}
]
}
集合中的第二个绑定名为 res
。 此 http
绑定是用于写入 HTTP 响应的输出绑定 (out
)。
若要从此函数写入 Azure 存储队列,请添加类型为 queue
、名称为 msg
的 out
绑定,如以下代码所示:
{
"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"
}
]
}
在这种情况下,msg
将作为输出参数提供给函数。 对于 queue
类型,还必须在 queueName
中指定队列的名称,并在 connection
中提供 Azure 存储连接的名称(来自 local.settings.json 文件)。
在 C# 项目中,绑定被定义为函数方法上的绑定属性。 具体定义取决于应用是在进程内(C# 类库)还是在隔离进程中运行。
打开 HttpExample.cs 项目文件,并将以下参数添加到 方法定义中:
[Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg,
msg
参数为一个 ICollector<T>
类型,表示函数完成时写入到输出绑定的消息集合。 在这种情况下,输出是名为的 outqueue
存储队列。 StorageAccountAttribute
设置存储帐户的连接字符串。 此属性指示包含存储帐户连接字符串的设置,可以在类、方法或参数级别应用。 在本例中,可以省略 StorageAccountAttribute
,因为你已使用默认存储帐户。
Run 方法定义现在必须如以下代码所示:
[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
[Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg,
ILogger log)
在 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
})
添加使用 msg
输出绑定对象来创建队列消息的代码。 请在方法返回之前添加此代码。
if (!string.IsNullOrEmpty(name))
{
// Add a message to the output collection.
msg.Add(name);
}
此时,你的函数一定如下所示:
[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
[Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
if (!string.IsNullOrEmpty(name))
{
// Add a message to the output collection.
msg.Add(name);
}
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
现在可以使用新的 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 运行时和队列输出绑定中可以方便地处理所有这些集成任务。
在本地运行函数
通过从 LocalFunctionProj 文件夹启动本地 Azure Functions 运行时主机来运行函数。
func start
在输出的末尾,必须要显示以下行:
注意
如果 HttpExample 未按如上所示出现,则可能是在项目的根文件夹外启动了主机。 在这种情况下,请按 Ctrl+C 停止主机,转至项目的根文件夹,然后重新运行上一命令。
将此输出中的 HTTP 函数的 URL 复制到浏览器,并追加查询字符串
?name=<YOUR_NAME>
,使完整 URL 类似于http://localhost:7071/api/HttpExample?name=Functions
。 浏览器应显示回显查询字符串值的响应消息。 当你发出请求时,启动项目时所在的终端还会显示日志输出。完成后,按 Ctrl + C 并键入
y
以停止函数主机。
提示
在启动过程中,主机会下载并安装存储绑定扩展和其他 Microsoft 绑定扩展。 之所以安装这些扩展,是因为默认情况下,已在 host.json 文件中使用以下属性启用了绑定扩展:
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
如果遇到任何与绑定扩展相关的错误,请检查上述属性是否在 host.json 中存在。
查看 Azure 存储队列中的消息
可以在 Azure 门户或 Azure 存储资源管理器中查看队列。 也可以按以下步骤中所述,在 Azure CLI 中查看队列:
打开函数项目的 local.setting.json 文件,并复制连接字符串值。 在终端或命令窗口中运行以下命令以创建名为
AZURE_STORAGE_CONNECTION_STRING
的环境变量,并粘贴特定的连接字符串来代替<MY_CONNECTION_STRING>
。 (创建此环境变量后,无需在使用--connection-string
参数的每个后续命令中提供连接字符串。)export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
(可选)使用
az storage queue list
命令查看帐户中的存储队列。 此命令的输出一定包含名为outqueue
的队列,该队列是函数将其第一条消息写入该队列时创建的。az storage queue list --output tsv
使用
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 中验证
像在上一篇快速入门中一样,使用浏览器或 CURL 来测试重新部署的函数。
按上一部分所述再次检查存储队列,验证它是否包含已写入到其中的新消息。
清理资源
完成后,请使用以下命令删除资源组及其包含的所有资源,以免产生额外的费用。
az group delete --name AzureFunctionsQuickstart-rg
后续步骤
现已更新 HTTP 触发的函数,使其将数据写入存储队列。 现在,可以详细了解如何使用 Core Tools 和 Azure CLI 通过命令行进行 Functions 开发: