Amazon 简单通知服务脚本






4.60/5 (4投票s)
自动响应 Amazon 简单通知服务消息
引言
Amazon Simple Notification Service (SNS) 是一项Web服务,允许您通过将消息推送到分布式应用程序来通知它们。
假设您有一个运行在许多服务器上的应用程序。在传统的应用程序中,计算机将持续轮询队列或数据库以查找要处理的任务。许多计算机的持续轮询会降低队列的性能并导致更高的使用费用。您可以增加轮询间隔以减少负载,但这会降低应用程序的性能。
一种让您的计算机立即处理任务并消除轮询队列需求的方法是使用 Amazon 的 Simple Notification Service。首先,您需要在运行应用程序的每台计算机上设置一个Web服务器。接下来,在 Amazon 中设置一个 SNS 订阅,将消息发送到您的计算机。然后,在您的Web服务器上创建一个应用程序,以便在收到 SNS 消息时执行某些操作。最后,配置您的应用程序,以便在需要执行某些操作时调用 SNS。
这是流程的表示。
您的应用程序 à Amazon SNS à 您的Web服务器 à 执行工作
要熟悉 Amazon SNS,您可以使用您的 Amazon 凭证登录 AWS 控制台。首先创建一个主题,然后添加一个指定消息将发送到何处的订阅。您可以将消息发送到电子邮件地址、网站URL或 Amazon Simple Queue Service 队列。
创建订阅时,您需要确认您有权向那里发送消息。对于电子邮件订阅,您将收到一封电子邮件,需要点击该电子邮件中的链接。创建Web URL订阅时,Amazon 会向该URL发送一条消息。您需要捕获此消息,并通过加载消息中的特殊URL或使用消息中包含的令牌调用API来响应它。如果您不确认订阅,Amazon 将不会向其发送消息。
配置Web URL以接收 SNS 消息的过程很棘手。您需要在设置订阅之前配置Web服务器。以下描述了如何设置 Windows Web服务器以响应 Amazon SNS 消息。本文档的主要内容是设置一个脚本以自动确认 SNS 订阅并验证 SNS 消息是否来自 Amazon。
Using the Code
首先下载附带的 Visual Studio 项目。您需要 Visual Studio 2010 来处理它。该项目包含一个名为 AutoConfirm.aspx 的文件。这是您将 SNS 订阅指向的脚本。在代码的开头,您将看到存储您的 Amazon AccessKeyId
和 SecretAccessKey
的变量。用您的值填充这些变量。接下来,您将看到与电子邮件设置相关的变量。该脚本配置为在接收到消息时发送电子邮件。用您的设置填充这些值。
String AWSAccessKeyId = "xxxxxxxxxxxxxxxxxxxxx";
String AWSSecretAccessKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
String EmailFromAddress = "account@gmail.com";
String EmailToAddress = "me@company.com";
String EmailSmtpHost = "smtp.gmail.com";
int EmailSmtpPort = 587;
Boolean EmailSmtpEnableSsl = true;
String EmailSmtpUserName = "account@gmail.com";
String EmailSmtpPassword = "mypassowrd";
脚本首先收集发送给它的所有信息。SNS 消息的一个有趣之处在于,您无法通过典型的 Request.Form
或 Request.QueryString
方法访问发送给您的数据。发送给您脚本的信息只能通过 Request.InputStream
获得。代码演示了如何将 Request.InputStream
转换为 string
。
//The LogMessage will contain information about what the script did.
//It will be sent through email at the end.
String LogMessage = "";
LogMessage += "DateTime = " + DateTime.Now + Environment.NewLine;
LogMessage += "SERVER_NAME = " + Request.ServerVariables["SERVER_NAME"] +
Environment.NewLine;
LogMessage += "LOCAL_ADDR = " + Request.ServerVariables["LOCAL_ADDR"] +
Environment.NewLine;
LogMessage += "REMOTE_ADDR = " + Request.ServerVariables["REMOTE_ADDR"] +
Environment.NewLine;
LogMessage += "HTTP_USER_AGENT = " + Request.ServerVariables["HTTP_USER_AGENT"] +
Environment.NewLine;
LogMessage += "CONTENT_TYPE = " + Request.ServerVariables["CONTENT_TYPE"] +
Environment.NewLine;
LogMessage += "SCRIPT_NAME = " + Request.ServerVariables["SCRIPT_NAME"] +
Environment.NewLine;
LogMessage += "REQUEST_METHOD = " + Request.ServerVariables["REQUEST_METHOD"] +
Environment.NewLine;
LogMessage += "Request.QueryString = " + Request.QueryString.ToString() +
Environment.NewLine;
LogMessage += "Request.Form = " + Request.Form.ToString() + Environment.NewLine;
//Convert the Request.InputStream into a string.
byte[] MyByteArray = new byte[Request.InputStream.Length];
Request.InputStream.Read(MyByteArray, 0, Convert.ToInt32(Request.InputStream.Length));
String InputStreamContents;
InputStreamContents = System.Text.Encoding.UTF8.GetString(MyByteArray);
此时,我们已经获得了发送给我们的数据。Amazon 以 JSON 格式发送消息。脚本使用 System.Web.Script.Serialization.JavaScriptSerializer
类将 JSON 消息转换为 Dictionary
。您可以通过在项目中添加对 System.Web.Extensions.dll 的引用来访问此类。该类仅在 .NET Framework 4.0 中可用。如果您的Web服务器不支持 .NET 4.0,您将需要重写此部分代码,以便使用其他方法从消息中解析变量。
//Convert the JSON data into a Dictionary.
//Use of the System.Web.Script namespace requires
//a reference to System.Web.Extensions.dll.
System.Web.Script.Serialization.JavaScriptSerializer MyJavaScriptSerializer =
new System.Web.Script.Serialization.JavaScriptSerializer();
System.Collections.Generic.Dictionary<String, Object> MyObjectDictionary;
MyObjectDictionary = MyJavaScriptSerializer.DeserializeObject(InputStreamContents)
as System.Collections.Generic.Dictionary<String, Object>;
现在所有变量都在一个 dictionary
中,访问它们很简单。例如,使用 MyObjectDictionary["TopicArn"].ToString()
来获取消息中 TopicArn
的值。
接下来,代码验证消息是否确实来自 Amazon。这是通过获取消息中的值,然后按照特定顺序构建一个 string
来完成的。此 string
的构建在 http://sns-public-resources.s3.amazonaws.com/SNS_Message_Signing_Release_Note_Jan_25_2011.pdf 中进行了描述。Amazon 在其端构建了相同的 string
,并在发送消息给您之前对其进行了签名。签名的 string
值包含在消息的 Signature
值中。您将使用这些值来确认签名是否有效。
StringBuilder MyStringBuilder = new StringBuilder();
MyStringBuilder.Append("Message\n");
MyStringBuilder.Append(MyObjectDictionary["Message"].ToString()).Append("\n");
MyStringBuilder.Append("MessageId\n");
MyStringBuilder.Append(MyObjectDictionary["MessageId"].ToString()).Append("\n");
MyStringBuilder.Append("SubscribeURL\n");
MyStringBuilder.Append(MyObjectDictionary["SubscribeURL"].ToString()).Append("\n");
MyStringBuilder.Append("Timestamp\n");
MyStringBuilder.Append(MyObjectDictionary["Timestamp"].ToString()).Append("\n");
MyStringBuilder.Append("Token\n");
MyStringBuilder.Append(MyObjectDictionary["Token"].ToString()).Append("\n");
MyStringBuilder.Append("TopicArn\n");
MyStringBuilder.Append(MyObjectDictionary["TopicArn"].ToString()).Append("\n");
MyStringBuilder.Append("Type\n");
MyStringBuilder.Append(MyObjectDictionary["Type"].ToString()).Append("\n");
String GeneratedMessage;
GeneratedMessage = MyStringBuilder.ToString();
VerifySignature
函数下载 Amazon 的公共证书密钥,并使用它来创建消息中变量的哈希。如果哈希与 Signature
值匹配,则消息确实来自 Amazon。只有 Amazon 拥有生成签名所需的私有证书密钥。
private static Boolean VerifySignature
(String GeneratedMessage, String SignatureFromAmazon, String SigningCertURL)
{
System.Uri MyUri = new System.Uri(SigningCertURL);
//Check if the domain name in the SigningCertURL is an Amazon URL.
if (MyUri.Host.EndsWith(".amazonaws.com") == true)
{
byte[] SignatureBytes;
SignatureBytes = Convert.FromBase64String(SignatureFromAmazon);
//Check the cache for the Amazon signing cert.
byte[] PEMFileBytes;
PEMFileBytes = (byte[])System.Web.HttpContext.Current.Cache[SigningCertURL];
if (PEMFileBytes == null)
{
//Download the Amazon signing cert and save it to cache.
System.Net.WebClient MyWebClient = new System.Net.WebClient();
PEMFileBytes = MyWebClient.DownloadData(SigningCertURL);
System.Web.HttpContext.Current.Cache[SigningCertURL] = PEMFileBytes;
}
System.Security.Cryptography.X509Certificates.X509Certificate2 MyX509Certificate2 =
new System.Security.Cryptography.X509Certificates.X509Certificate2(PEMFileBytes);
System.Security.Cryptography.RSACryptoServiceProvider MyRSACryptoServiceProvider;
MyRSACryptoServiceProvider =
(System.Security.Cryptography.RSACryptoServiceProvider)MyX509Certificate2.PublicKey.Key;
System.Security.Cryptography.SHA1Managed MySHA1Managed =
new System.Security.Cryptography.SHA1Managed();
byte[] HashBytes = MySHA1Managed.ComputeHash(Encoding.UTF8.GetBytes(GeneratedMessage));
return MyRSACryptoServiceProvider.VerifyHash
(HashBytes, System.Security.Cryptography.CryptoConfig.MapNameToOID("SHA1"),
SignatureBytes);
}
else
{
return false;
}
}
如果签名有效,代码会自动响应 ConfirmSubscription
请求。在创建订阅后,您将立即收到一个 ConfirmSubscription
请求。该消息包含一个您将用于确认订阅的特殊令牌。代码通过调用 ConfirmSubscription
API 方法来实现这一点。有关此方法的信息可以在 http://docs.amazonwebservices.com/sns/latest/api/API_ConfirmSubscription.html 中找到。代码使用 SprightlySoft
AWS 组件来发送 API 请求。此组件是免费的,并且可以轻松地将所需的授权参数发送到 API。有关更多信息,请参见 http://sprightlysoft.com/AWSComponent/。
Boolean RetBool;
int ErrorNumber = 0;
String ErrorDescription = "";
String LogData = "";
Dictionary<String, String> RequestHeaders = new Dictionary<String, String>();
int ResponseStatusCode = 0;
String ResponseStatusDescription = "";
Dictionary<String, String> ResponseHeaders = new Dictionary<String, String>();
String ResponseString = "";
//This a SubscriptionConfirmation message. Get the token.
String Token;
Token = MyObjectDictionary["Token"].ToString();
//Get the endpoint from the SigningCertURL value.
String TopicEndpoint;
TopicEndpoint = MyObjectDictionary["SigningCertURL"].ToString();
TopicEndpoint = TopicEndpoint.Substring
(0, TopicEndpoint.IndexOf(".amazonaws.com/") + 15);
//Amazon Simple Notification Service, ConfirmSubscription:
<a href="http://docs.amazonwebservices.com/sns/latest/api/
API_ConfirmSubscription.htmlString">http://docs.amazonwebservices.com/
sns/latest/api/API_ConfirmSubscription.html
String</a> RequestURL;
RequestURL = TopicEndpoint + "?Action=ConfirmSubscription&Version=2010-03-31";
RequestURL += "&Token=" + System.Uri.EscapeDataString(Token);
RequestURL += "&TopicArn=" + System.Uri.EscapeDataString(TopicArn);
//Use the SprightlySoft AWS Component to call ConfirmSubscription on Amazon.
RetBool = MakeAmazonSignatureVersion2Request(AWSAccessKeyId,
AWSSecretAccessKey, RequestURL, "GET", null, "", 3, ref ErrorNumber,
ref ErrorDescription, ref LogData, ref RequestHeaders, ref ResponseStatusCode,
ref ResponseStatusDescription, ref ResponseHeaders, ref ResponseString); ;
代码查看 API 的响应。成功的请求将导致一个包含 SubscriptionArn
值的 XML 文档。
if (RetBool == true)
{
System.Xml.XmlDocument MyXmlDocument;
System.Xml.XmlNamespaceManager MyXmlNamespaceManager;
System.Xml.XmlNode MyXmlNode;
MyXmlDocument = new System.Xml.XmlDocument();
MyXmlDocument.LoadXml(ResponseString);
MyXmlNamespaceManager = new System.Xml.XmlNamespaceManager(MyXmlDocument.NameTable);
MyXmlNamespaceManager.AddNamespace("amz", "http://sns.amazonaws.com/doc/2010-03-31/");
MyXmlNode = MyXmlDocument.SelectSingleNode
("amz:ConfirmSubscriptionResponse/amz:ConfirmSubscriptionResult/
amz:SubscriptionArn", MyXmlNamespaceManager);
LogMessage += "ConfirmSubscription was successful. SubscriptionArn = " +
MyXmlNode.InnerText + Environment.NewLine;
LogMessage += Environment.NewLine;
}
else
{
LogMessage += "ConfirmSubscription failed." + Environment.NewLine;
LogMessage += "Response Status Code: " + ResponseStatusCode + Environment.NewLine;
LogMessage += "Response Status Description: " + ResponseStatusDescription +
Environment.NewLine;
LogMessage += "Response String: " + ResponseString + Environment.NewLine;
LogMessage += Environment.NewLine;
}
脚本最后发送一封电子邮件,其中包含有关所发生情况的信息。这封电子邮件可以轻松地看到您的脚本何时被调用以及发生了什么。
//Send an email with the log information.
System.Net.Mail.SmtpClient MySmtpClient = new System.Net.Mail.SmtpClient();
MySmtpClient.Host = EmailSmtpHost;
MySmtpClient.Port = EmailSmtpPort;
MySmtpClient.EnableSsl = EmailSmtpEnableSsl;
if (EmailSmtpUserName != "")
{
System.Net.NetworkCredential MyNetworkCredential = new System.Net.NetworkCredential();
MyNetworkCredential.UserName = EmailSmtpUserName;
MyNetworkCredential.Password = EmailSmtpPassword;
MySmtpClient.Credentials = MyNetworkCredential;
}
System.Net.Mail.MailAddress FromMailAddress =
new System.Net.Mail.MailAddress(EmailFromAddress);
System.Net.Mail.MailAddress ToMailAddress =
new System.Net.Mail.MailAddress(EmailToAddress);
System.Net.Mail.MailMessage MyMailMessage =
new System.Net.Mail.MailMessage(FromMailAddress, ToMailAddress);
if (TopicArn == "")
{
MyMailMessage.Subject = "Log Information For " +
Request.ServerVariables["SERVER_NAME"] + Request.ServerVariables["SCRIPT_NAME"];
}
else
{
MyMailMessage.Subject = "Log Information For " + TopicArn;
}
MyMailMessage.Body = LogMessage;
MySmtpClient.Send(MyMailMessage);
现在您知道了脚本的工作原理,是时候将其发布到Web服务器了。该脚本需要一个运行 .NET Framework 4.0 的 Windows Web服务器。Web服务器必须可以从Internet访问,以便 Amazon 可以向其发送消息。
发布脚本后,您可以在 Amazon 中设置 SNS 订阅。转到 AWS 控制台 (http://aws.amazon.com/console/) 并创建一个新主题。拥有主题后,在其下创建一个订阅。对于协议,选择 HTTP。对于端点,输入您发布的脚本的URL。

如果一切顺利,您的订阅将立即得到确认,您将收到脚本发送的包含详细信息的电子邮件。然后,您就可以向您的订阅发布消息了。
关于 SprightlySoft
本文由 SprightlySoft 提供。SprightlySoft 为 Microsoft 开发人员开发工具和技术,以便更轻松地使用 Amazon Web Services。请访问 http://sprightlysoft.com/ 获取更多信息。