使用 Azure Serverless Functions 实现 SMS API
使用 Azure 函数实现无服务器的免费 SMS Web API。
引言
SMS API 由三个主要部分组成,它们按如下方式组合在一起
- SMS 提供商:有各种第三方 SMS 提供商允许向任何手机发送 SMS,而且免费。对于 Azure Function 的这部分,我们选择 160by2.com SMS 提供商,它允许免费向任何手机发送 SMS。
- 屏幕抓取:SMS 提供商不允许在未实际访问网站的情况下发送 SMS。使用 C# 代码,我们将提交登录表单,然后以编程方式提交发送 SMS 的 Web 表单。
- Azure 上的无服务器函数:托管在 Azure 上的无服务器函数允许我们设置 Web API,任何 REST 客户端都可以使用它。
背景
在阅读本文之前,请先了解以下内容:
- 什么是无服务器
- Azure 无服务器函数简介
- 使用 C# 进行 Web 抓取
- CsQuery:使用 .NET 进行类似 jQuery 的 DOM 操作
Using the Code
在我们深入使用 Azure Function 编写 SMS Web API 之前,我们需要了解将要执行的操作顺序。
SmsWebClient
类继承自 C# 的 WebClient
类,它允许我们以编程方式发送 HTTP
POST
/GET
方法。
我们将对 160by2.com SMS 提供商实现 HTTP POST
和 GET
方法的编程执行。160by2.com 是一个免费的 SMS 提供商,您需要获取用户名和密码才能发送 SMS。SMS 提供商类包含 Login()
和 SendSms()
函数来处理主要工作。我们使用 CsQuery
库来执行 HTML DOM 操作。
在 160by2.com 网站上,我们有一个包含用户名和密码的登录表单。
当我们检查 Web 表单的 HTML 时,我们可以看到有两个 ID 分别为 username
和 password
的输入字段。
当我们单击登录按钮时,表单数据将使用 HTTP POST
方法提交。
为了以编程方式完成此操作,我们将创建一个 C# NameValueCollection
,并添加 Web 表单键的值,然后使用 WebClient
的 UploadValues
方法提交表单。
var Client = new WebClient();
string loginPage = "http://www.160by2.com/re-login";
NameValueCollection data = new NameValueCollection();
data.Add("username", UserName); //username input field
data.Add("password", Password); //password input field
Client.UploadValues(loginPage, "POST", data);//submit the form
发送 SMS 表单在 UI 中仅包含手机号码和消息。
为了检查提交此 Web 表单时提交了哪些表单值,我们将使用 Google Chrome 控制台,您也可以使用 Fiddler。
按F12打开 Chrome 开发者控制台,然后转到网络选项卡,并在 UI 中,通过单击“立即发送”来提交发送 SMS 表单。提交的请求如下所示:
就像登录表单一样,我们需要以编程方式提交这些表单数据键及其值。
var base_url = "http://www.160by2.com/";
var recipient = "8888YOURNUMBER";
var message = "This is test SMS message";
string cookieVal = CookieJar.GetCookies(new Uri(base_url))["JSESSIONID"].Value.Substring
(cookieVal.IndexOf('~') + 1); //we need to read the session id value from cookies
//send by the server while logging in
//load the send sms web form
CQ sendSmsPage = Client.DownloadString(base_url + "SendSMS?id=" + cookieVal);
NameValueCollection data = new NameValueCollection();
//find keys for all inputs in the form
CQ form = sendSmsPage.Find("form[id=frm_sendsms]");
CQ inputs = form.Find("input[type=hidden]");
foreach (var input in inputs)
{
CQ inp = input.Cq();
data.Add(inp.Attr("name"), inp.Attr("value"));
}
//mobile number input
CQ mobileNumberBox = form.Find("input[placeholder='Enter Mobile Number or Name']")[0].Cq();
data.Add(mobileNumberBox.Attr("name"), recipient);
//textarea for message input
data.Add("sendSMSMsg", message);
string sendSmsPost = base_url + data["fkapps"];
data["hid_exists"] = "no";
data["maxwellapps"] = cookieVal;
//additional vals
data.Add("messid_0", "");
data.Add("messid_1", "");
data.Add("messid_2", "");
data.Add("messid_3", "");
data.Add("messid_4", "");
data.Add("newsExtnUrl", "");
data.Add("reminderDate", DateTime.Now.ToString("dd-MM-yyyy"));
data.Add("sel_hour", "");
data.Add("sel_minute", "");
data.Add("ulCategories", "29");
Client.UploadValues(sendSmsPost, data);//submit the send sms form
最终类如下所示:
using CsQuery;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Text;
using System.Linq;
namespace azuresmsapp
{
public class OneSixtybyTwo
{
public string UserName { get; set; }
public string Password { get; set; }
private CookieContainer CookieJar { get; set; }
private SmsWebClient Client { get; set; }
private string base_url = "http://www.160by2.com/";
private bool IsLoggedIn = false;
public OneSixtybyTwo(string username, string password)
{
UserName = username;
Password = password;
CookieJar = new CookieContainer();
Client = new SmsWebClient(CookieJar, false);
}
public bool Login()
{
string loginPage = base_url + "re-login";
NameValueCollection data = new NameValueCollection();
data.Add("rssData", "");
data.Add("username", UserName);
data.Add("password", Password);
byte[] loginResponseBytes = Client.UploadValues(loginPage, "POST", data);
CQ loginResponse = System.Text.Encoding.UTF8.GetString(loginResponseBytes);
IsLoggedIn = loginResponse.Find("[type=password]").Count() == 0;
return IsLoggedIn;
}
public bool SendSms(string recipient, string message)
{
if (IsLoggedIn == false)
throw new Exception("Not logged in");
string cookieVal = CookieJar.GetCookies(new Uri(base_url))["JSESSIONID"].Value;
cookieVal = cookieVal.Substring(cookieVal.IndexOf('~') + 1);
CQ sendSmsPage = Client.DownloadString(base_url + "SendSMS?id=" + cookieVal);
NameValueCollection data = new NameValueCollection();
//all inputs
CQ form = sendSmsPage.Find("form[id=frm_sendsms]");
CQ inputs = form.Find("input[type=hidden]");
foreach (var input in inputs)
{
CQ inp = input.Cq();
data.Add(inp.Attr("name"), inp.Attr("value"));
}
//sms input
CQ mobileNumberBox =
form.Find("input[placeholder='Enter Mobile Number or Name']")[0].Cq();
data.Add(mobileNumberBox.Attr("name"), recipient);
//textarea
data.Add("sendSMSMsg", message);
string sendSmsPost = base_url + data["fkapps"];
data["hid_exists"] = "no";
data["maxwellapps"] = cookieVal;
//additional vsls
data.Add("messid_0", "");
data.Add("messid_1", "");
data.Add("messid_2", "");
data.Add("messid_3", "");
data.Add("messid_4", "");
data.Add("newsExtnUrl", "");
data.Add("reminderDate", DateTime.Now.ToString("dd-MM-yyyy"));
data.Add("sel_hour", "");
data.Add("sel_minute", "");
data.Add("ulCategories", "29");
Client.UploadValues(sendSmsPost, data);
return true;
}
}
}
现在,我们的主菜完成了,加上了樱桃……
要发送 SMS,我们创建一个 OneSixtybyTwo
类的实例,调用 Login
函数,然后调用 SendSMS
函数。
OneSixtybyTwo objSender = new OneSixtybyTwo ("160BY2.COM_USERNAME", "160BY2.COM_PASSWORD");
if (objSender.Login()) {
var sendResult = objSender.SendSms(number, message);
}
让我们深入了解具有 HTTP 触发器的 Azure 无服务器函数。
该函数可以通过 HTTP GET
或 POST
方法启动,因此我们使用以下代码读取发布的手机号码和消息:
string number = req.Query["number"];
string message = req.Query["message"];
string requestBody = new StreamReader(req.Body).ReadToEnd();
dynamic data = JsonConvert.DeserializeObject(requestBody);
number = number ?? data?.number;
message = message ?? data?.message;
最终函数如下所示:
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
using System;
namespace azuresmsapp
{
public static class SendSMS
{
[FunctionName("SendSMS")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function,
"get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
try
{
log.Info("C# HTTP trigger function processed a request.");
string number = req.Query["number"];
string message = req.Query["message"];
string requestBody = new StreamReader(req.Body).ReadToEnd();
dynamic data = JsonConvert.DeserializeObject(requestBody);
number = number ?? data?.number;
message = message ?? data?.message;
OneSixtybyTwo objSender = new OneSixtybyTwo
("160BY2.COM_USERNAME", "160BY2.COM_PASSWORD");
if (objSender.Login())
{
var sendResult = objSender.SendSms(number, message);
if (sendResult)
{
return (ActionResult)new OkObjectResult($"Message sent");
}
else
{
throw new Exception($"Sending failed");
}
}
else
{
throw new Exception("Login failed");
}
}
catch (System.Exception ex)
{
return new BadRequestObjectResult("Unexpected error, " + ex.Message);
}
}
}
}
API 的 Angular 客户端
我将使用 Angular 7 应用程序作为 Web API 的客户端。您可以使用任何您想要的客户端。
在我们使用 API 之前,我们需要允许来自所有来源的请求访问 API。
为此,请导航到 Azure Function => 单击平台功能 => 单击 CORS。
删除现有条目并添加新条目 '*
',如下所示:
现在,在 Angular 7 客户端中发送 SMS,我们编写以下代码:
//pseudo code
import { Component } from '@angular/core';
import { Message } from './dtos/message';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public message:Message;
private baseUrl = "https://YOUR_FUNCTION_URL_HERE";
constructor(private httpClient: HttpClient){
this.message = {
message: "",
number: ""
};
}
Send(){
alert("Sending sms...");
this.httpClient.get(this.baseUrl + '&number=' + this.message.number +
'&message=' + this.message.message).subscribe((x)=>{}, (y)=>{},()=>{
alert("Message sent successfully!");
this.message = {
message: "",
number: ""
};
});
}
}
用户界面仅包含手机号码和消息。
伪 Angular7 客户端演示应用程序可在以下网址找到:Stackblitz & Github
您也可以下载附带的源代码文件。
关注点
- CsQuery:
CsQuery
库允许我们进行类似 jQuery 的 DOM 操作。 - SMS 提供商:有许多 SMS 提供商允许免费发送 SMS。我使用屏幕抓取实现了一些,该项目可在 github 上找到。
- Fiddler Web 调试器:Fiddler 允许检查提交的 Web 表单。
历史
- 2019 年 4 月 5 日:初稿
- 2019 年 4 月 6 日:添加了 Angular 客户端代码
- 2019 年 4 月 8 日:关于代码的更多详细信息