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: