Simple Http Server

The Http Listener device is designed to receive data payloads and store them in a downstream queue. Typically, payloads/messages are sent to Connexion for processing. However, the Http Listener device can also be used as a simple http server (returning a payload). In this scenario, the Http Listener would be the only device within the channel (since we are not receiving a payload, we have nothing to store on the queue).

In this example, we will create a server which inspects a GET request and returns some dummy data. We will use the Custom Actions tab to inject our custom logic.

Create a new channel and add an Http Listener device. Remove all other devices. Configure the Connection and Options tabs as required. In this example, we’re using the following configuration:

On the Custom Actions tab, overwrite the existing boilerplate code with the following, and add a reference to System.Web.

using System; using System.IO; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.Web; namespace Connexion.Device.HttpListener { //the class name must be 'HttpHandler' public class HttpHandler : BaseHttpHandler { // dummy data private readonly Dictionary<string, string> _Products = new Dictionary<string, string>() { { "1", "Product with id 1" }, { "2", "Product with id 2" }, { "3", "Product with id 3" }, }; // dummy data private readonly Dictionary<string, string> _Users = new Dictionary<string, string>() { { "1", "User with id 1" }, { "2", "User with id 2" }, { "3", "User with id 3" }, }; public override async Task AfterMessageReceivedAsync(MessageReceivedEventArgs args) { // gets the page requested var path = args.HttpContext.Request.Url.AbsolutePath.ToLowerInvariant(); // gets the querystring as a dictionary var queryDictionary = GetQuerystringAsDictionary(args.HttpContext.Request.Url.Query.ToLowerInvariant()); switch (path) { case "/products": await HandleProductRequest(args, queryDictionary); break; case "/users": await HandleUserRequest(args, queryDictionary); break; } } // turn the querystring into a dictionary private Dictionary<string, string> GetQuerystringAsDictionary(string querystring) { var queryValues = HttpUtility.ParseQueryString(querystring); var queryDictionary = new Dictionary<string, string>(queryValues.Count, StringComparer.InvariantCultureIgnoreCase); foreach (var key in queryValues.AllKeys) { queryDictionary[key] = queryValues[key]; } return queryDictionary; } private async Task HandleProductRequest(MessageReceivedEventArgs args, Dictionary<string, string> queryValues) { try { // we're expecting a query with key = "id" if (!queryValues.TryGetValue("id", out var queryIdString)) throw new Exception("Query did not contain an id. Expected /products?id=X"); // ensure we have the id within our dictionary if (!_Products.TryGetValue(queryIdString, out var product)) throw new Exception($"No product found with id '{queryIdString}'"); using (var sw = new StreamWriter(args.HttpContext.Response.OutputStream)) await sw.WriteAsync(product); } catch(Exception ex) { // optionally customize your status code & description args.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; args.HttpContext.Response.StatusDescription = ex.Message; throw; } } private async Task HandleUserRequest(MessageReceivedEventArgs args, Dictionary<string, string> queryValues) { try { // we're expecting a query with key = "id" if (!queryValues.TryGetValue("id", out var queryIdString)) throw new Exception("Query did not contain an id. Expected /products?id=X"); if (!_Users.TryGetValue(queryIdString, out var user)) throw new Exception($"No user found with id '{queryIdString}'"); using (var sw = new StreamWriter(args.HttpContext.Response.OutputStream)) await sw.WriteAsync(user); } catch (Exception ex) { args.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; args.HttpContext.Response.StatusDescription = ex.Message; throw; } } } }

At the top of the class, we’re defining two dictionaries to hold some dummy data. We’re then overriding the AfterMessageReceivedAsync method to parse the uri that was requested. In our example, we have two paths: /products will return a product based on the id query (http://myserver:8444/products?id=2), and /users, which acts in the same fashion (http://myserver:8444/users?id=3).

The helper method GetQuerystringAsDictionary will parse the uri query into a dictionary (and requires a reference to System.Web).

Start the channel (making sure all the port reservations have been run) and open Postman. Submit some GET requests and note the responses.

We can see that it’s fairly straight forward to inspect the uri that was sent to the Http Listener device and return specific content based on the path and/or query.

Here is another simple sample which uses the Connexion API to return the state of connexion channels. Add a reference to Connexion.Api and Connexion.Api.Shared, and use the following code:

using System; using System.IO; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.Web; using Connexion.Api; using Connexion.Api.Common; namespace Connexion.Device.HttpListener { public class HttpHandler : BaseHttpHandler { public override async Task AfterMessageReceivedAsync(MessageReceivedEventArgs args) { // gets the page requested var path = args.HttpContext.Request.Url.AbsolutePath.ToLowerInvariant(); // gets the querystring as a dictionary var queryDictionary = GetQuerystringAsDictionary(args.HttpContext.Request.Url.Query.ToLowerInvariant()); switch (path) { case "/channel": await HandleChannelRequest(args, queryDictionary); break; case "/channels": await HandleChannelsRequest(args); break; } } private Dictionary<string, string> GetQuerystringAsDictionary(string querystring) { var queryValues = HttpUtility.ParseQueryString(querystring); var queryDictionary = new Dictionary<string, string>(queryValues.Count, StringComparer.InvariantCultureIgnoreCase); foreach (var key in queryValues.AllKeys) { queryDictionary[key] = queryValues[key]; } return queryDictionary; } // http://localserv/channel?key=X private async Task HandleChannelRequest(MessageReceivedEventArgs args, Dictionary<string, string> queryValues) { try { // we're expecting a query with key = "key" if (!queryValues.TryGetValue("key", out var channelKeyString)) throw new Exception("Query did not contain an key. Expected /channel?key=channelkey"); if(!Guid.TryParse(channelKeyString, out var channelKey)) throw new Exception("key value was not a valid GUID"); string result; using(var proxy = ConnexionApiProxyFactory.CreateProxy()) { var channel = await proxy.ServiceMethods.GetChannelAsync(new GetChannelRequest(channelKey)); if(channel == null) throw new Exception($"No channel found with key '{channelKey}'"); result = $"'{channel.Channel.ChannelName}' is {channel.Channel.ActualState}"; } using (var sw = new StreamWriter(args.HttpContext.Response.OutputStream)) await sw.WriteAsync(result); } catch(Exception ex) { args.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; args.HttpContext.Response.StatusDescription = ex.Message; throw; } } // http://localserv/channels private async Task HandleChannelsRequest(MessageReceivedEventArgs args) { try { using (var sw = new StreamWriter(args.HttpContext.Response.OutputStream)) using(var proxy = ConnexionApiProxyFactory.CreateProxy()) { var result = await proxy.ServiceMethods.GetChannelsAsync(new GetChannelsRequest { }); if(result == null) throw new Exception($"Channel query failed"); foreach(var channel in result.Channels) { await sw.WriteLineAsync($"Channel '{channel.ChannelName}' is {channel.ActualState}"); } } } catch(Exception ex) { args.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; args.HttpContext.Response.StatusDescription = ex.Message; throw; } } } }

This produces the following output: