As I’m still looking around for solutions to create some sort of easy mechanism of capturing all HTTP requests generated from IE, I ran across the HttpListener class in .NET 2.0. HttpListener is essentially a Web Server class, that’s super easy to use.
So figuring that an HTTP Proxy is really more or a less glorified HTTP server with a forwarding mechanism I played around with this a bit today and it seems to work in easily capturing output. Here’s a simple wrapper I threw together earlier to make accessing the server a little easier yet. With this code you can simply add a this class to a form and either implement ReceiveWebRequest event or else subclass this class and and override the ProcessRequest method.
Here’s the wrapper class in C# code:
using System;
using System.Text;
using System.Net;
using System.IO;
using Westwind.Tools;
namespace Westwind.InternetTools
{
public delegate void delReceiveWebRequest(HttpListenerContext Context);
/// <summary>
/// Wrapper class for the HTTPListener to allow easier access to the
/// server, for start and stop management and event routing of the actual
/// inbound requests.
/// </summary>
public class HttpServer
{
protected HttpListener Listener;
protected bool IsStarted = false;
public event delReceiveWebRequest ReceiveWebRequest;
public HttpServer()
{
}
/// <summary>
/// Starts the Web Service
/// </summary>
/// <param name="UrlBase">
/// A Uri that acts as the base that the server is listening on.
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/
/// Note: the trailing backslash is required! For more info see the
/// HttpListener.Prefixes property on MSDN.
/// </param>
public void Start(string UrlBase)
{
// *** Already running - just leave it in place
if (this.IsStarted)
return;
if (this.Listener == null)
{
this.Listener = new HttpListener();
}
this.Listener.Prefixes.Add(UrlBase);
this.IsStarted = true;
this.Listener.Start();
IAsyncResult result = this.Listener.BeginGetContext( new AsyncCallback(WebRequestCallback), this.Listener );
}
/// <summary>
/// Shut down the Web Service
/// </summary>
public void Stop()
{
if (Listener != null)
{
this.Listener.Close();
this.Listener = null;
this.IsStarted = false;
}
}
protected void WebRequestCallback(IAsyncResult result)
{
if (this.Listener == null)
return;
// Get out the context object
HttpListenerContext context = this.Listener.EndGetContext(result);
// *** Immediately set up the next context
this.Listener.BeginGetContext(new AsyncCallback(WebRequestCallback), this.Listener);
if (this.ReceiveWebRequest != null)
this.ReceiveWebRequest(context);
this.ProcessRequest(context);
}
/// <summary>
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
/// <param name="Context"></param>
protected virtual void ProcessRequest(HttpListenerContext Context)
{
}
}
}
To try this out throw a textbox and a couple buttons on the form, name the textbox txtUrl and btnStart, btnStop.
public partial class Form1 : Form
{
public HttpServer Server = null;
public Form1()
{
InitializeComponent();
Server = new HttpProxyServer();
}
private void btnStart_Click(object sender, EventArgs e)
{
this.Server.Start(this.txtUrl.Text);
}
private void btnStop_Click(object sender, EventArgs e)
{
this.Server.Stop();
}
}
In my case I’m testing out the behavior leading up to acting like a proxy, so I created a subclass called HttpProxy Server like this (which right now is merely echoing back the headers):
public class HttpProxyServer : HttpServer
{
protected override void ProcessRequest(System.Net.HttpListenerContext Context)
{
HttpListenerRequest Request = Context.Request;
HttpListenerResponse Response = Context.Response;
StringBuilder sb = new StringBuilder();
sb.AppendLine(Request.HttpMethod + " " + Request.RawUrl + " Http/" + Request.ProtocolVersion.ToString());
if (Request.UrlReferrer != null)
sb.AppendLine("Referer: " + Request.UrlReferrer);
if (Request.UserAgent != null)
sb.AppendLine("User-Agent: " + Request.UserAgent);
for (int x = 0; x < Request.Headers.Count; x++)
{
sb.AppendLine(Request.Headers.Keys[x] + ":" + " " + Request.Headers[x]);
}
sb.AppendLine();
string Output = "<html><body><h1>Hello world</h1>Time is: " + DateTime.Now.ToString() +
"<pre>" + sb.ToString() + "</pre>";
byte[] bOutput = System.Text.Encoding.UTF8.GetBytes(Output);
Response.ContentType = "text/html";
Response.ContentLength64 = bOutput.Length;
Stream OutputStream = Response.OutputStream;
OutputStream.Write(bOutput, 0, bOutput.Length);
OutputStream.Close();
}
}
If you run this thing it will service HTTP requests by simply echoing back the input headers and all so fancy HelloWorld message. Yeah, cheesy I know and woefully incomplete even for what it does in terms of request capturing, but it’s really nice to be able to do all of this with just a few lines of code. Obviously doing something useful in response of the HTTP request might get more complex quickly. Also, realize this is not meant as a scalable server – this wrapper uses a single async to fire a request and although it starts up a new context quite quickly, for a more scalable solution you probably need to set up a Thread Pool. Then again if you stick this sort of thing into your application I expect you’re not looking for an ultra-scalable mechanism but merely for a quick way to pass request/response messages back and forth.
I can think of a number of uses that I will have for this from easy machine to machine communication that can now literally be handled with just a few lines of code on each end for the data passing end at least. Note though that this class requires Windows XP SP 2 or Windows Server 2003 (or Vista I presume) to work. I haven't checked exactly what the dependencies are, but it definitely works without IIS running. The docs don't mention much so I assume it must work without other OS requirements activated.
Beyond that there’s my proxy/request capturing problem <g> which led me here in the first place. With this I can easily capture the HTTP input response, capture the request data I need and then fire up an HTTPWebRequest to actually forward the request to the original request Url (specified in the hostname), capturing the output then writing it back to the client. Now that’s a lot of overhead, but it sure is lots easier then getting bogged down in HTTP protocol details like continue headers, chunked responses and the keep alive management etc. that make using Raw Sockets for this not as easy as it may seem. In fact several of the Proxy Server Samples I looked at fell apart as soon as the browser would send a non-sequential request header…
Other Posts you might also like