Using the C# httpServer with raised events

I am writing some applications at the moment which require a lot of data gathering and there are a number of people at the events who want to be able to see this data while it is being collected. I have TCP socket connections for the main data collection and these pass their data to a central server. In order to avoid having to create further clients to access the collected data, I decided to run a small web server as part of the central server and just create web pages to view the data. Other people would then be able to edit and style these pages without any interference from me. As it happens there were no others available, so the users had to put up with my web design.

I made a class based around the MSDN sample and comments, but wanted to extend it in two ways; firstly, I needed to be able serve up the full gamut of binary files for any of the image types in the documents; secondly, I wanted to allow a command to be sent to the hosting application, via an http command. I added an event handler to do this. This would then decouple any application logic from being involved on the web side. I can also use it to report unknown file requests.

The source and a zipped binary are on github.com, https://github.com/happyt/csharp/tree/master/httpWebServer

This is the main class definition, just edit out the parts labelled RaiseEvents if that is not needed, together with the delegate declaration.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;

namespace httpWebServer
{
///

/// To allow messages to be sent to the host app
///

///
///
public delegate void clientEventHandler(object sender, HttpCommandArgs e);

public class HttpWebServer
{
///

/// Some pages for reference chat
/// http://stackoverflow.com/questions/427326/httplistener-server-header-c
/// http://msdn.microsoft.com/en-us/library/system.net.httplistener.aspx
/// http://www.dreamincode.net/forums/topic/215467-c%23-httplistener-and-threading/

///

private HttpListener listener;
public string portNumber = "8081";
private const string rootPath = @"./web";
private const string defaultPage = @"default.htm";

//===================================================================
// RaiseEvents code
// to raise the message received
public event clientEventHandler controlMessage;

///

/// Invoke the message received event
///

///
protected virtual void OnCommand(HttpCommandArgs e)
{
// check to see that there is at least one event handler listening
if (controlMessage != null) controlMessage(this, e);
}

///

/// Set up the parameters and raise a Reply message event
///

/// Message to be raised
private void RaiseCommand(string strMessage)
{
HttpCommandArgs s = new HttpCommandArgs();
s.m_Message = strMessage;
OnCommand(s);
}

//===================================================================

///

/// Creates the http server and opens listener
///

public void Start()
{
listener = new HttpListener();
listener.Prefixes.Add("http://*:" + portNumber + "/");
listener.Start();
listener.BeginGetContext(ProcessRequest, listener);
Console.WriteLine("Connection Started");
}

///

/// Stops listening
///

public void Stop()
{
listener.Stop();
}

///

/// Process the web request
///

///
private void ProcessRequest(IAsyncResult result)
{
string responseString = "";
string responseType = "text/html";
string filetype = "";
bool binary = false;
byte[] buffer;
int pos;

HttpListener listener = (HttpListener)result.AsyncState;
HttpListenerContext context = listener.EndGetContext(result);
HttpListenerRequest request = context.Request;

string path = rootPath + request.RawUrl;
if (path == rootPath + "/") path += "default.htm";

filetype = Path.GetExtension(path);
switch (filetype)
{
case (".png"):
case (".jpg"):
case (".jpeg"):
case (".gif"):
case (".ico"):
responseType = "image/" + filetype.Substring(1); // leave off the decimal point
binary = true;
break;

case (".htm"):
case (".html"):
case (".css"):
responseType = "text/html";
binary = false;
break;

case (".js"):
responseType = "application/javascript";
binary = false;
break;

case (".xml"):
responseType = "text/" + filetype.Substring(1); // leave off the decimal point
binary = false;
break;

case (".json"):
responseType = "application/json";
binary = false;
break;

default:
break;
}

HttpListenerResponse response = context.Response;
if (File.Exists(path))
{
if (binary)
{
//Open image as byte stream to send to requestor
FileInfo fInfo = new FileInfo(path);
long numBytes = fInfo.Length;
FileStream fStream = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader binaryReader = new BinaryReader(fStream);
buffer = binaryReader.ReadBytes((int)numBytes);
}
else
{
StreamReader streamReader = new StreamReader(path);
responseString = streamReader.ReadToEnd();
buffer = Encoding.UTF8.GetBytes(responseString);
}
}
//====================================================
// RaiseEvents code
else if ((pos = path.IndexOf("cmd=", 0)) > 0)
{
string command = path.Substring(pos + 4);
RaiseCommand(command);
responseString = "OK";
buffer = Encoding.UTF8.GetBytes(responseString);
}
//====================================
else
{
responseString = "Unknown file: " + request.RawUrl + "";
RaiseCommand("Unknown");
buffer = Encoding.UTF8.GetBytes(responseString);
}

response.ContentType = responseType;
response.ContentLength64 = buffer.Length;
Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();

listener.BeginGetContext(ProcessRequest, listener);
}
}
}

The message being raised can be any class that you’d like to create, but in my case I just used a simple message, HttpCommandArgs.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace httpWebServer
{
///

/// Used with socket handling messages
///

public class HttpCommandArgs
{
public string m_Message;
}
}

I tested this in a simple winForms application, where I start the server and wait for commands. The web side is self contained and there is nothing to be done in the host application, other than to create and start the server. Browse to the local host (on port 80 in my case) and the default page will be shown. It has a couple of image types on there, a javascript script to show the time, a style sheet and an icon, making sure that all the main types are served.

If a url such as “localhost/cmd=Counting” is used, the command will be raised to the host application and displayed. I do nothing with the commands other than display them in the form; to do this I need to use an Invoke because the application is returning the message on a different thread.

The C# code for the host application is ,

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using httpWebServer;

namespace webTest
{
public partial class Form1 : Form
{

///

/// For browser status page
///

private HttpWebServer http;

///

/// Reply to update form
///

///
public delegate void SetTextCallback(string text);

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
try
{
StopStartWeb();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

//======================================================================

///

/// Handle a button click to start/stop the service
///

///
///
private void btnStartStop_Click(object sender, EventArgs e)
{
StopStartWeb();

MessageBox.Show("After...");

}

///

/// Start/Stop the http service
///

private void StopStartWeb()
{
try
{
if (http == null)
{
http = new HttpWebServer();
http.portNumber = "80";
http.Start();
btnStartStop.Text = "Stop http";
http.controlMessage += new clientEventHandler(http_controlMessage);
}
else
{
http.Stop();
btnStartStop.Text = "Start http";
}
}
catch (Exception ex)
{
MessageBox.Show("Closing http error: " + ex.Message);
}
}

///

/// Event handler for returned messages
///

///
///
void http_controlMessage(object sender, HttpCommandArgs e)
{
try
{
SetStatus(e.m_Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

// Deal with the http command
ParseCommand(e.m_Message);
}

///

/// Do any work for a command in this section
///

///
private void ParseCommand(string strCommand)
{

}

//=======================================================================

///

/// Display a reply from the server
///

///
private void SetStatus(string text)
{
string s = text;

if (this.labStatus.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetStatus);
this.Invoke(d, new object[] { text });
}
else
{
this.labStatus.Text = "";
LogMessage(s);
}
}

///

/// For a logging section
/// just put into a scrolling list for now
///

///
private void LogMessage(string s)
{
this.txtHistory.Text += s + Environment.NewLine;
this.txtHistory.SelectionStart = txtHistory.Text.Length;
this.txtHistory.ScrollToCaret();
}
}
}

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s