65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Azure Serverless Functions 实现 SMS API

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.82/5 (9投票s)

2019年4月5日

CPOL

3分钟阅读

viewsIcon

34108

downloadIcon

250

使用 Azure 函数实现无服务器的免费 SMS Web API。

引言

SMS API 由三个主要部分组成,它们按如下方式组合在一起

  1. SMS 提供商:有各种第三方 SMS 提供商允许向任何手机发送 SMS,而且免费。对于 Azure Function 的这部分,我们选择 160by2.com SMS 提供商,它允许免费向任何手机发送 SMS。
  2. 屏幕抓取:SMS 提供商不允许在未实际访问网站的情况下发送 SMS。使用 C# 代码,我们将提交登录表单,然后以编程方式提交发送 SMS 的 Web 表单。
  3. Azure 上的无服务器函数:托管在 Azure 上的无服务器函数允许我们设置 Web API,任何 REST 客户端都可以使用它。

背景

在阅读本文之前,请先了解以下内容:

  1. 什么是无服务器
  2. Azure 无服务器函数简介
  3. 使用 C# 进行 Web 抓取
  4. CsQuery:使用 .NET 进行类似 jQuery 的 DOM 操作

Using the Code

在我们深入使用 Azure Function 编写 SMS Web API 之前,我们需要了解将要执行的操作顺序。

Sequence diagram for Azure serverless function - click to enlarge image

SmsWebClient 类继承自 C# 的 WebClient 类,它允许我们以编程方式发送 HTTP POST/GET 方法。

我们将对 160by2.com SMS 提供商实现 HTTP POSTGET 方法的编程执行。160by2.com 是一个免费的 SMS 提供商,您需要获取用户名和密码才能发送 SMS。SMS 提供商类包含 Login()SendSms() 函数来处理主要工作。我们使用 CsQuery 库来执行 HTML DOM 操作。

在 160by2.com 网站上,我们有一个包含用户名和密码的登录表单。

当我们检查 Web 表单的 HTML 时,我们可以看到有两个 ID 分别为 usernamepassword 的输入字段。

当我们单击登录按钮时,表单数据将使用 HTTP POST 方法提交。

为了以编程方式完成此操作,我们将创建一个 C# NameValueCollection,并添加 Web 表单键的值,然后使用 WebClientUploadValues 方法提交表单。

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 GETPOST 方法启动,因此我们使用以下代码读取发布的手机号码和消息:

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。

删除现有条目并添加新条目 '*',如下所示:

cors in Azure function - click to enlarge image

现在,在 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: ""
    };
        });
  }
}

用户界面仅包含手机号码和消息。

sms ui

 

伪 Angular7 客户端演示应用程序可在以下网址找到:Stackblitz & Github

您也可以下载附带的源代码文件。

关注点

  1. CsQueryCsQuery 库允许我们进行类似 jQuery 的 DOM 操作。
  2. SMS 提供商:有许多 SMS 提供商允许免费发送 SMS。我使用屏幕抓取实现了一些,该项目可在 github 上找到。
  3. Fiddler Web 调试器Fiddler 允许检查提交的 Web 表单。

历史

  • 2019 年 4 月 5 日:初稿
  • 2019 年 4 月 6 日:添加了 Angular 客户端代码
  • 2019 年 4 月 8 日:关于代码的更多详细信息
© . All rights reserved.