Amazon Simple Notification Service (SNS) is a web service that allows you to notify distributed applications by pushing a message to them.
Say you have an application that runs on many servers. In a traditional application, the computers would continually poll a queue or database looking for a job to process. Many computers continually polling will reduce performance of the queue and result in higher usage fees. You can increase the time between polls to reduce load but this decreases the performance of your application.
One way to have your computers process a job immediately and eliminate the need to poll a queue is to use Amazon’s Simple Notification Service. First, you set up a web server on each of the computers that run your application. Next, set up a SNS subscription in Amazon to send messages to your computers. Then create an application on your web servers to do something when they receive an SNS message. Finally, configure your application to call SNS when something needs to be done.
Here is a representation of the flow.
Your application à Amazon SNS à Your web servers à Do work
To get familiar with Amazon SNS, you can log into the AWS Console with your Amazon credentials. Start by creating a Topic and add a Subscription that will specify where the messages will go. You can send a message to an email address, web site URL, or an Amazon Simple Queue Service queue.
When you create a subscription, you will need to confirm you have permission to send messages there. With an email subscription you will be sent an email and you will need to click a link in that email. When you create a web URL subscription, Amazon sends a message to that URL. You will need to capture this message and respond to it by loading a special URL in the message or calling the API with a token that is included in the message. If you do not confirm the subscription, Amazon will not send messages to it.
The process of configuring a web URL to receive a SNS message is tricky. You need to configure the web server before you set up the subscription. The following describes how to set up a Windows web server to respond to Amazon SNS messages. The main points of the article are setting up a script to automatically confirm a SNS subscription and to verify that the SNS message came from Amazon.
Using the Code
Start by downloading the attached Visual Studio project. You will need Visual Studio 2010 to work with it. The project contains a file called AutoConfirm.aspx. This is the script you will point your SNS subscription to. At the beginning of the code, you will see variables that hold your Amazon
SecretAccessKey. Fill these variables with your values. Next, you will see variables related to email settings. The script is configured to send an email whenever a message is received. Fill these values with your settings.
String AWSAccessKeyId = "xxxxxxxxxxxxxxxxxxxxx";
String AWSSecretAccessKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
String EmailFromAddress = "email@example.com";
String EmailToAddress = "firstname.lastname@example.org";
String EmailSmtpHost = "smtp.gmail.com";
int EmailSmtpPort = 587;
Boolean EmailSmtpEnableSsl = true;
String EmailSmtpUserName = "email@example.com";
String EmailSmtpPassword = "mypassowrd";
The script starts by gathering all information that was sent to it. An interesting fact of SNS messages is that you cannot access the data sent to you through the typical
Request.QueryString methods. Information sent to your script will only be available through
Request.InputStream. The code demonstrated how to convert
Request.InputStream into a
String LogMessage = "";
LogMessage += "DateTime = " + DateTime.Now + Environment.NewLine;
LogMessage += "SERVER_NAME = " + Request.ServerVariables["SERVER_NAME"] +
LogMessage += "LOCAL_ADDR = " + Request.ServerVariables["LOCAL_ADDR"] +
LogMessage += "REMOTE_ADDR = " + Request.ServerVariables["REMOTE_ADDR"] +
LogMessage += "HTTP_USER_AGENT = " + Request.ServerVariables["HTTP_USER_AGENT"] +
LogMessage += "CONTENT_TYPE = " + Request.ServerVariables["CONTENT_TYPE"] +
LogMessage += "SCRIPT_NAME = " + Request.ServerVariables["SCRIPT_NAME"] +
LogMessage += "REQUEST_METHOD = " + Request.ServerVariables["REQUEST_METHOD"] +
LogMessage += "Request.QueryString = " + Request.QueryString.ToString() +
LogMessage += "Request.Form = " + Request.Form.ToString() + Environment.NewLine;
byte MyByteArray = new byte[Request.InputStream.Length];
Request.InputStream.Read(MyByteArray, 0, Convert.ToInt32(Request.InputStream.Length));
InputStreamContents = System.Text.Encoding.UTF8.GetString(MyByteArray);
At this point, we have the data that was sent to us. Amazon sends the message in JSON format. The script converts the JSON message into a
Dictionary using the
System.Collections.Generic.Dictionary<String, Object> MyObjectDictionary;
as System.Collections.Generic.Dictionary<String, Object>;
Now that all the variables are in a
dictionary, it is simple to access them. For example, use
MyObjectDictionary["TopicArn"].ToString() to give you the value of
TopicArn in the message.
Next, the code validates that the message really did come from Amazon. This is done by taking the values in the message, and then constructing a
string with the values in a specific order. The construction of this
string is described at http://sns-public-resources.s3.amazonaws.com/SNS_Message_Signing_Release_Note_Jan_25_2011.pdf. Amazon constructed the same
string on their end and signed the
string before sending the message to you. The signed
string value is included in the
Signature value of the message. You will use these values to confirm the signature is valid.
StringBuilder MyStringBuilder = new StringBuilder();
GeneratedMessage = MyStringBuilder.ToString();
VerifySignature function downloads Amazon’s public certificate key and uses it to create a hash of the variables in the message. If the hash matches the
Signature value, then the message did come from Amazon. Only Amazon has the private certificate key which is required to generate signatures.
private static Boolean VerifySignature
(String GeneratedMessage, String SignatureFromAmazon, String SigningCertURL)
System.Uri MyUri = new System.Uri(SigningCertURL);
if (MyUri.Host.EndsWith(".amazonaws.com") == true)
SignatureBytes = Convert.FromBase64String(SignatureFromAmazon);
PEMFileBytes = (byte)System.Web.HttpContext.Current.Cache[SigningCertURL];
if (PEMFileBytes == null)
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 =
System.Security.Cryptography.SHA1Managed MySHA1Managed =
byte HashBytes = MySHA1Managed.ComputeHash(Encoding.UTF8.GetBytes(GeneratedMessage));
If the signature is valid, the code automatically responds to
ConfirmSubscription requests. You get a
ConfirmSubscription request as soon as you create a subscription. The message contains a special token you will use to confirm the subscription. The code does this by calling the
ConfirmSubscription API method. Information on this method can be found at http://docs.amazonwebservices.com/sns/latest/api/API_ConfirmSubscription.html. The code uses the
SprightlySoft AWS Component to make the API request. This component is free and it makes it easy to send the required authorization parameters to the API. See http://sprightlysoft.com/AWSComponent/ for more information.
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 = "";
Token = MyObjectDictionary["Token"].ToString();
TopicEndpoint = MyObjectDictionary["SigningCertURL"].ToString();
TopicEndpoint = TopicEndpoint.Substring
(0, TopicEndpoint.IndexOf(".amazonaws.com/") + 15);
RequestURL = TopicEndpoint + "?Action=ConfirmSubscription&Version=2010-03-31";
RequestURL += "&Token=" + System.Uri.EscapeDataString(Token);
RequestURL += "&TopicArn=" + System.Uri.EscapeDataString(TopicArn);
RetBool = MakeAmazonSignatureVersion2Request(AWSAccessKeyId,
AWSSecretAccessKey, RequestURL, "GET", null, "", 3, ref ErrorNumber,
ref ErrorDescription, ref LogData, ref RequestHeaders, ref ResponseStatusCode,
ref ResponseStatusDescription, ref ResponseHeaders, ref ResponseString); ;
The code looks at the response from the API. A successful request will result in an XML document with the
if (RetBool == true)
MyXmlDocument = new System.Xml.XmlDocument();
MyXmlNamespaceManager = new System.Xml.XmlNamespaceManager(MyXmlDocument.NameTable);
MyXmlNode = MyXmlDocument.SelectSingleNode
LogMessage += "ConfirmSubscription was successful. SubscriptionArn = " +
MyXmlNode.InnerText + Environment.NewLine;
LogMessage += Environment.NewLine;
LogMessage += "ConfirmSubscription failed." + Environment.NewLine;
LogMessage += "Response Status Code: " + ResponseStatusCode + Environment.NewLine;
LogMessage += "Response Status Description: " + ResponseStatusDescription +
LogMessage += "Response String: " + ResponseString + Environment.NewLine;
LogMessage += Environment.NewLine;
The script finishes by sending an email with information on what took place. This email makes it easy to see when your script is called and what took place.
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 =
System.Net.Mail.MailAddress ToMailAddress =
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"];
MyMailMessage.Subject = "Log Information For " + TopicArn;
MyMailMessage.Body = LogMessage;
Now that you know how the script works, it’s time to publish it to a web server. The script requires a Windows web server running the .NET Framework 4.0. The web server must be accessible from the Internet in order for Amazon to send messages to it.
When you have published the script, you can set up an SNS subscription in Amazon. Go to the AWS Console (http://aws.amazon.com/console/) and create a new topic. Once you have a topic, create a subscription under it. For protocol, select HTTP. Under endpoint, enter the URL to the script you published.
If everything went right, your subscription will immediately be confirmed and you will receive an email from the script with details. You will then be able to publish messages to your subscription.
This article has been brought to you by SprightlySoft. SprightlySoft develops tools and techniques for Microsoft developers to more easily work with Amazon Web Services. Visit http://sprightlysoft.com/ for more information.