Using Azure to SSL-Enable an Http REST Service
The internet is full of interesting (and often free) REST services that expose countless new data sources and operations to client-side applications. A week doesn’t go by where I’m not playing with some new service for a client-side app I’m building. One challenge I’ve encountered is when I’m building an SSL-enabled app against a service with no https end-point. Most browsers will block client-side REST calls with mixed protocols. This is particularly troublesome when developing apps for SharePoint and Office, which require SSL (as any app should that passes around access tokens). In this post, I will outline a simple approach I use to proxy http REST services through Azure for SSL. Azure does most of the work by delivering websites with free SSL end-points (at least if you can live with the default https://*.azurewebsites.net domain).
The sample below is about as simple as it gets. We make the HttpWebRequest to the REST service within a WCF service (could just as easily use Web API). The json returned from the http REST service is simply passed through to the original client request in the return statement (but as https).
Simple Pass-through Proxy
[ServiceContract][AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]public class Cars{ [OperationContract] [WebGet()] public string GetByVIN(string vin) { HttpWebRequest myRequest = (HttpWebRequest)HttpWebRequest.Create(String.Format("https://api.edmunds.com/v1/api/toolsrepository/vindecoder?vin={0}&api_key={1}&fmt=json", vin, ConfigurationManager.AppSettings["EdmundsAPIKey"])); myRequest.Method = "GET"; using (WebResponse response = myRequest.GetResponse()) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { return reader.ReadToEnd(); } } }} |
Sometimes this technique is helpful for SSL-enabling AND trimming some of the unused content from a 3rd party service. In this sample I use JSON.NET to deserialize the json and return only the data I needed for my client-side application.
Trim Data in Proxy
[ServiceContract][AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]public class Rhymes{ [OperationContract] [WebGet()] public List<string> GetRhymes(string text) { List<string> data = null; HttpWebRequest myRequest = (HttpWebRequest)HttpWebRequest.Create(String.Format("https://rhymebrain.com/talk?function=getRhymes&word={0}", text)); myRequest.Method = "GET"; using (WebResponse response = myRequest.GetResponse()) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { var json = reader.ReadToEnd(); var x = JsonConvert.DeserializeObject<List<WordItem>>(json); data = x.Select(i => i.word).ToList(); } } return data; }} public class WordItem{ public string word { get; set; } public int freq { get; set; } public int score { get; set; } public string flags { get; set; } public string syllables { get; set; }} |
Besides not being SSL-enabled, this service required a cross-domain http POST that didn’t work client-side. In this case, my app was also hosting the service, so re-publishing as a POST was fine (no longer cross-domain). However, I also converted it to a GET to demonstrate that technique and the power of the proxy.
Proxy as HTTP POST
[ServiceContract][AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]public class Sentiment{ [OperationContract] [WebGet()] public string SentimentGet(string text) { HttpWebRequest myRequest = (HttpWebRequest)HttpWebRequest.Create("https://text-processing.com/api/sentiment/"); myRequest.Method = "POST"; string data = "text=" + text; byte[] bytes = Encoding.UTF8.GetBytes(data); myRequest.ContentLength = bytes.Length; using (Stream requestStream = myRequest.GetRequestStream()) { requestStream.Write(bytes, 0, bytes.Length); requestStream.Flush(); requestStream.Close(); using (WebResponse response = myRequest.GetResponse()) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { return reader.ReadToEnd(); } } } } [OperationContract] [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] public string SentimentPost(string text) { HttpWebRequest myRequest = (HttpWebRequest)HttpWebRequest.Create("https://text-processing.com/api/sentiment/"); myRequest.Method = "POST"; string data = "text=" + text; byte[] bytes = Encoding.UTF8.GetBytes(data); myRequest.ContentLength = bytes.Length; using (Stream requestStream = myRequest.GetRequestStream()) { requestStream.Write(bytes, 0, bytes.Length); requestStream.Flush(); requestStream.Close(); using (WebResponse response = myRequest.GetResponse()) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { return reader.ReadToEnd(); } } } }} |
This technique also works for creating a REST end-point for any traditional SOAP web service you might find on the interwebs. The sample below created a REST end-point for an old stock quote service I’ve been using for over a decade.
Proxy Traditional SOAP Service
[ServiceContract][AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]public class Stocks{ [OperationContract] [WebGet()] public Quote GetQuote(string s) { using (StockService.StockQuoteSoapClient client = new StockService.StockQuoteSoapClient("StockQuoteSoap")) { var quote = client.GetQuote(s); XDocument doc = XDocument.Parse(quote); Quote q = new Quote(); q.symbol = s; q.last = Convert.ToDouble(doc.Descendants("Last").First().Value); q.change = Convert.ToDouble(doc.Descendants("Change").First().Value); q.prev_close = Convert.ToDouble(doc.Descendants("PreviousClose").First().Value); q.pct_change = (q.last - q.prev_close) / q.prev_close; return q; } }} public class Quote{ public string symbol { get; set; } public double last { get; set; } public double change { get; set; } public double prev_close { get; set; } public double pct_change { get; set; } } |
So there you have it…couldn’t be easier. You can download my solution HERE. You will notice I add all my services to the same project (even though they are unrelated)…I do this mainly because they are all utilities for me and to conserve Azure Websites (ex: if you only get 10 free). Don’t give up on that great service just because you run into client-side challenges…turn to Azure!