Azure Functions是建立在触发器和绑定的概念上的,虽然有一个Twilio绑定用于发送短信,但没有触发器用于接收信息或呼叫。不过,Azure Functions确实有一个HTTP触发器,你可以用它来接收Twilio的webhook请求。
在本教程中,你将学习如何使用Azure Functions响应Twilio webhooks。你将学习什么是webhooks,如何设置Azure Function,然后使用你的Azure Function应用来响应Twilio SMS webhook。
什么是Webhook
Webhook是一个HTTP请求,由源系统向目标系统的事件触发。你通过提供一个URL来告诉源系统将把HTTP请求发送到哪里。
一个典型的webhook场景包括:
- 一个希望得到事件通知的目标系统
- 一个想要通知目标系统一个事件的源系统 - 这个系统接受webhook URL,并在事件发生时向该URL发出HTTP请求。
- 一个事件的细节(消息)。
webhook用例的一个例子是在循环计费的支付系统中。假设你的应用程序的一个用户无法使用你的应用程序上的一些服务,因为他们没有更新他们的付款。然后这个用户使用你的支付系统进行支付。在这种情况下,当付款成功时,你将希望得到支付系统的通知,这样你就可以为这个用户解除服务限制。这就是webhook发挥作用的地方。你向支付系统提供webhook URL,这样当支付成功时,这个URL就会被触发,并向你的应用程序发送一个包含支付信息的消息。然后,这条消息可以用来解除用户之前无法使用的服务的限制。
因此,源系统定义了可以触发的事件,目的系统在配置的webhook URL上接收消息。
各种Twilio产品有多个webhooks,但在本文中,你将专注于响应由传入短信触发的webhooks。
对于Twilio SMS,有两种类型的webhooks可以被触发:
- 传入的消息:当你的Twilio电话号码收到一条消息时,这个webhook会被触发。Twilio会把这个消息的细节发送到你指定的webhook URL。
- 状态回调:当通过Twilio发送的消息的状态改变时,这个webhook会被触发。这个webhook会发送一条消息,其中包括消息的状态以及关于该消息的其他细节。
Webhook的URL必须是公开的,否则源系统无法向该URL发送HTTP请求。这也意味着其他任何人都可以向你的Webhook URL发送HTTP请求。通过 设置Twilio Webhook URLs的安全指南来验证HTTP请求是否来自Twilio。
在下一节中,你将开始构建Azure Function项目,该项目将对SMS webhook做出响应。
在本地创建一个Azure函数
Azure Functions是Azure提供的无服务器事件驱动的服务,可以运行称为函数的轻量级代码,可以通过HTTP、Timer、队列等触发器进行调用。
Azure Functions是建立在触发器和绑定的概念上的,虽然有一个Twilio绑定来发送短信,但没有触发器来接收信息或呼叫。不过,正如你前面所学到的,Twilio使用webhooks,你可以用Azure Functions的HTTP触发器来处理webhook HTTP请求。
首先,创建一个新的文件夹,用以下命令在本地初始化一个Azure Function项目:
mkdir TwilioSmsWebhook
cd TwilioSmsWebhook
func init --dotnet
func init --dotnet 命令创建Azure Function项目,使用.NET作为其运行时间,C#作为编程语言。
运行下面的命令来创建将被Incoming Message webhook触发的函数:
func new --name IncomingMessage --template "HTTP trigger" --authlevel "anonymous"
--name参数接受函数的唯一名称,该名称将成为URL路径的一部分*。*--template参数接受你要使用的模板的名称。你可以使用func templates list来列出模板,特别是使用func templates list --language C#来列出C#模板。顾名思义,HTTP trigger模板使用HTTP触发器和HTTP绑定来接收HTTP请求并发回HTTP响应。这就是你在本教程中要处理的webhook HTTP请求。你可以在微软文档中查看其他类型的触发器和绑定。--authlevel指定了调用该函数的授权级别。你可以使用其他的授权级别,但anonymous,就可以了。
func new 命令生成了一个函数,当请求URL时,该函数会以默认信息进行响应。
要在本地启动该函数,请运行:
func start
这个命令建立了.NET项目,并在本地Azure Functions主机中运行该应用程序。一旦启动,它将打印出项目中的函数列表,包括基于HTTP触发器的函数的URL。
复制打印出来的IncomingMessage 函数的URL,它看起来像http://localhost:/api/IncomingMessage,然后在你的网络浏览器中导航到该URL以触发该函数。浏览器将显示像下面这样的信息。
This HTTP triggered function executed successfully. Pass a name in the query string or the request body for a personalized response.
按CTRL + C ,停止该函数应用程序。
接下来,让我们更新一下函数的C#代码,以响应传入的消息。
用Azure函数响应传入的短信
当你的Twilio电话号码收到一条消息时,Twilio会向你的webhook URL发送一个包含消息细节的HTTP请求。除了接收请求外,Twilio还希望你能用指令来回应Twilio的执行。你用Twilio标记语言(TwiML)来写这些指令。例如,下面的TwiML将以 "Ahoy!"来回应发送者。
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Message>Ahoy!</Message>
</Response>
你可以使用C#字符串甚至XML .NET APIs生成这些TwiML指令,但Twilio SDK for .NET有专门的API来生成TwiML。使用.NET CLI添加Twilio NuGet包。
dotnet add package Twilio
Twilio实验室还维护着另一个库,即ASP.NET的Twilio辅助库,其中的TwiMLResult 类将把TwiML写到HTTP响应体中。添加Twilio.AspNet.Core NuGet包。
dotnet add package Twilio.AspNet.Core
现在,用以下代码更新IncomingMessage 类。
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Twilio.TwiML;
using Twilio.Http;
using Twilio.AspNet.Core;
namespace TwilioSmsWebhook
{
public static class IncomingMessage
{
[FunctionName("IncomingMessage")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
ILogger log
)
{
var form = await req.ReadFormAsync();
var body = form["Body"];
var response = new MessagingResponse();
response.Message(
$"You sent: {body}",
action: new Uri("/api/MessageStatus", UriKind.Relative),
method: HttpMethod.Post
);
return new TwiMLResult(response);
}
}
}
使用HttpTrigger 属性,该函数被配置为由HTTP请求触发,请求将被绑定到HttpRequest req 参数。
在Twilio中,你可以配置SMS webhook HTTP请求,使用GET或POST方法发送。在本教程中,你将使用POST方法,这意味着消息细节将被序列化为HTTP请求正文中的一个表单。该函数使用ReadFormAsync 方法从表单主体中反序列化消息的细节。然后,"Body" 表单参数被检索出来供以后使用。
接下来,该函数构建了一个MessagingResponse 对象,这是Twilio SDK提供的构建TwiML的类之一。要把消息发回给发件人,你可以使用Message 方法;在这种情况下,消息回应的是发件人发给你的Twilio电话号码的内容。每当消息的状态发生变化时,Twilio将发送一个webhook HTTP请求到你传递给action 参数的URI。这个action 参数也被称为状态回调webhook。method 告诉Twilio使用HTTP POST方法发送状态更新。
如上面的代码所示,你不需要指定一个完整的URL。相反,当你只传入URL的路径时,Twilio将解析相对于当前来源的路径(://:)。
目前,没有任何东西监听*/api/MessageStatus*这个路径,所以这将导致404错误,但你将在后面实现。
最后,该函数返回一个TwiMLResult ,它负责将对象序列化为TwiML并写入HTTP响应。
在本地测试你的Azure Function webhook
为了测试这个webhook,你必须在互联网上公开你的本地URL,因为Twilio无法访问你的本地主机。ngrok是一个免费的工具,可以通过安全隧道将本地端口暴露在互联网上。
首先,使用func start 命令启动你的功能应用。
然后在另一个终端,运行以下命令来启动ngrok。
ngrok http <PORT>
用你的Azure Function在本地运行的端口替换<PORT> 。
ngrok命令会打印一个转发URL,看起来像d152-41-184-42-209.eu.ngrok.io。
现在你有了一个公共URL,让我们在Twilio中配置Webhook URL。
登录到Twilio控制台,选择你的账户。点击 "探索 产品",选择电话号码。然后在左侧导航中,点击 电话号码**> 管理 > 活动号码**。

点击你想使用的活动号码,这将带你到配置页面
在配置页面上,向下滚动到信息传递 部分。 在 "A MESSAGE COMES IN "下,将第一个下拉框设置为Webhook,然后在文本框中输入ngrok Forwarding URL,路径为*/api/IncomingMessage*,最后将第二个下拉框设置为HTTP POST。

点击保存,然后向你的Twilio电话号码发送一条短信。
下面是一个短信对话的例子:

干得好!你已经成功地回复了一条来电信息。
ngrok 命令应该显示对*/api/IncomingMessage的成功请求200 OK ,但也对/api/MessageStatus*的请求404 Not Found 。Twilio正在为状态回调发送一个HTTP请求,但你还没有实现这个。
让ngrok命令继续运行,但使用CTRL + C 停止Function应用程序。现在,让我们来实现Status Callback Webhook。
实现状态回调Webhook
Status Callback webhook将在一个外发消息的状态发生变化时通知你。
与Incoming Message webhook不同,Status Callback webhook URL被配置在外发信息上而不是电话号码上。
如果你配置了状态回拨webhook使用HTTP POST方法发送,数据将被格式化编码,看起来像这样:
SmsSid: SM2xxxxxx
SmsStatus: sent
MessageStatus: sent
To: +1512zzzyyyy
MessageSid: SM2xxxxxx
AccountSid: ACxxxxxxx
From: +1512xxxyyyy
ApiVersion: 2010-04-01
当你配置Incoming Message webhook或Status Callback webhook使用HTTP GET方法发送时,数据将被传递到查询字符串中,而不是一个表单编码的主体。
为了实现状态回调,使用这个命令创建一个新的HTTP函数:
func new --name MessageStatus --template "HTTP trigger" --authlevel "anonymous"
记得action 参数,又称状态回调URL,在IncomingMessage 函数中配置了路径*/api/MessageStatus*?该URL指向这个新的MessageStatus 函数。
更新MessageStatus.cs文件,使其看起来像下面的代码:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace TwilioSmsWebHook
{
public class MessageStatus
{
[FunctionName("MessageStatus")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
ILogger log
)
{
var form = await req.ReadFormAsync();
string messageSid = form["MessageSid"];
string messageStatus = form["MessageStatus"];
log.LogInformation(
"Status changed to {MessageStatus} for Message {MessageSid}",
messageStatus,
messageSid
);
return new OkResult();
}
}
}
MessageStatus 函数将由一个HTTP请求触发,该请求被绑定到HttpRequest req 参数上。该函数的实现类似于IncomingMessage 函数。
使用req.ReadFormAsync() 读取请求正文,并检索MessageSid 和MessageStatus 属性以记录它们。
在这个例子中,你只是在记录它们,但在实际应用中,你可能会在数据库中记录你发出的消息,你可以在这里更新数据库中的消息状态。
与Incoming Message webhook不同,Twilio并不期望TwiML指令或任何指令作为对Status Callback webhook的响应。所以这个函数会通过返回一个OkResult ,返回一个HTTP状态200 OK的空响应。
要测试Status Callback webhook,请再次启动Function应用,但这次是用--verbose 参数,这样你就可以看到被记录的信息:
func start --verbose
然后向你的Twilio电话号码发送一条信息。在ngrok终端,你现在应该看到Status Callback URL的200 OK,你也应该看到MessageStatus 和MessageSid 记录到func start 命令中。
你已经成功实现了Twilio SMS产品的所有webhooks。
接下来,让我们把Azure Function应用部署到Azure。
将Azure函数部署到Azure
要部署到Azure,你必须在Azure上创建必要的资源。你将使用Azure CLI这样做。
使用az login 命令登录到Azure。
这将打开一个网页浏览器,在那里你可以登录到Azure门户。现在应该在终端上显示一个响应,显示有关该账户的详细信息。
然后使用以下命令创建一个资源组。
az group create \
--name <RESOURCE_GROUP_NAME> \
--location <REGION>
反斜杠(\)是用来很好地将命令参数格式化在单独的行上,用于类似bash的shell。对于CMD,你可以用圆点符号(^)代替(/),对于PowerShell,你可以用反斜杠符号(`)代替。
<RESOURCE_GROUP_NAME> 在你的Azure账户中必须是唯一的。<REGION> 是资源组所在的区域。运行az account list-locations -o table 命令会返回一个区域阵列供你选择。你应该总是选择离你的用户所在地较近的地区,以减少延时。
Azure Functions使用Azure Storage来保存数据。使用以下命令在这个资源组中创建一个存储账户。
az storage account create \
--name <STORAGE_ACCOUNT_NAME> \
--location <REGION> \
--resource-group <RESOURCE_GROUP_NAME> \
--sku Standard_LRS
<STORAGE_ACCOUNT_NAME> ,必须是Azure Storage上的全球唯一名称,而且必须只包含小写字母和数字。你可以指定与之前相同的区域。如果同一地区不支持,请尝试下一个最近的地区。
使用下面的命令,在Azure的资源组中用你之前创建的存储账户创建一个Function应用:
az functionapp create \
--resource-group <RESOURCE_GROUP_NAME> \
--consumption-plan-location <REGION> \
--runtime dotnet \
--functions-version 4 \
--name <APP_NAME> \
--storage-account <STORAGE_ACCOUNT_NAME>
将<RESOURCE_GROUP_NAME> 和<STORAGE_NAME> ,分别替换为你之前为资源组和Azure存储账户选择的名称。如果支持的话,你也可以指定与你在创建其他资源时指定的相同区域。
用一个全局唯一的名称替换<APP_NAME> 。<APP_NAME> 是作为Function应用的主机名的一部分。这条命令还指定了Function应用程序的版本,在这里是第四版。你可以在你的项目文件(.csproj)的AzureFunctionsVersion 属性中找到相同的版本号。
最后,为了部署应用程序,运行以下命令:
func azure functionapp publish <APP_NAME>
几分钟后,控制台中会显示几个URL,显示你创建的Azure Function应用的不同功能URL。在本教程中,将显示2个URL,看起来像:https://<APP_NAME>.azurewebsites.net/api/<FUNCTION_NAME>。
接下来,回到Twilio控制台,像之前那样用ngrok转发URL更新Incoming Message webhook URL。不要使用ngrok Forwarding URL,而是使用前一个命令所打印的IncomingMessage 函数的完整URL。现在你的Function应用已经在云端运行,你可以再次通过发送短信给你的Twilio电话号码来测试你的应用。
总结
在这篇文章中,你学到了如何使用Azure Functions响应Twilio Incoming Message webhook和Message Status Callback webhook。你还学会了如何使用ngrok在本地进行测试并将其部署到Azure。
你可以使用这段代码为你的公司建立伟大的产品,如FAQ机器人,或一个调度系统,你的客户可以发送一个消息与你预约。