Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Add a Web Server to your .NET 2.0 app with a few lines of code


:P
On this page:

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…

 

 


The Voices of Reason


 

dominick
December 04, 2005

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Hi Rick,

you forgot to mention that this code will only work as admin (or are you running as admin ?? :))

http://www.leastprivilege.com/HttpCfgACLHelper.aspx

cheers
dominick

Rick Strahl
December 05, 2005

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Thanks for posting that dominick. I guess you're becoming my security conscience around here <g>...

And yes, I run as admin unfortunately. I have too many applications (and most of them not mine <g>) that require admin rights, plus there's IIS admin where I spend much of my time during the day. It's not worth going through the pain of running with less privililege only to be running everything using RunAs...


Peter Bromberg
December 05, 2005

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Yay, Rick!

Now that's using yer header.

# .NET 2.0での簡易Webサーバの作成

Add a Web Server to your&nbsp; .NET 2.0&nbsp; app with a few lines of code
.NET Framework 2.0??????HttpLisetner????????????Web????????????????MSDN?????????????????????????????????????...

Sadiq
May 16, 2006

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Hi Rick,
I am making a proxy like yours one but I want that after catching the user request from browser, it should display the actual contents of the page requested in the user's browser. I used HTTPWebRequest and HTTPWebListener classes but it gives lots of errors, especially when there are images on the page. Can you give some idea.
My mail is chormara1@yahoo.com

Thanks


cDima
July 18, 2006

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Man, this is a cool class.

I'm thinking of using this for a front-end to a portal, exposed to a LAN of 100 users.

Could you tell me more about scalability of this approach? Lets say there are 4-6 database queries to generate one page. And there are at most 6 people using the page at a time. Will it die? :)

email: sadakov@gmail.com

Krishna
August 10, 2006

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Sorry If I'm asking anything stupid.
Why we need this at all, when we have IIS6.0 / IIS7.0 will all the security feature in.

Rick Strahl
August 10, 2006

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

For a server application I would agree 100%, but there are client and P2P sceanrios where this come in quite handy. IIS7 is a big deal to have running on a client machine and this avoids having to use it - although you still need extended rights to host the server.


Rick Strahl
October 05, 2006

# How can I capture all HTTP traffic on a specific port using code? - Rick Strahl

I'm trying to figure out how to capture all HTTP traffic on a specific port, so it can be played back at some later point. So today I'm picking your brain... maybe.

# DotNetSlackers: Add a Web Server to your .NET 2.0 app with a few lines of code


Rick Strahl's Web Log
October 10, 2006

# Article: A low-level look at the ASP.NET Architecture - Rick Strahl's Web Log

ASP.NET is a powerful platform for building Web applications, that provides a tremendous amount of flexibility and power for building just about any kind of Web application. Most people are familiar only with the high level frameworks like WebForms and WebServices which sit at the very top level of the ASP.NET hierarchy. This article looks at how Web requests flow through the ASP.NET framework from a very low level perspective, from Web Server, through ISAPI all the way up the request handler an

Kunal
December 19, 2006

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Hey Rick,

The article is cool ! However, can you be more clear about ReceiveWebRequest function ?
What would you do in that function ? Can you share a code snippet ?

Thanks,
Kunal (koolkunal@gmail.com)

# Google Groups: microsoft.public.dotnet.framework


Tagworld: fannyadams - Posts
January 07, 2007

# TagWorld :: fannyadams - .net


OpticTygre
December 06, 2007

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

One thing - Because the Listener.EndGetContext is blocking code - just in another thread - if you tried to stop this server, the main application would stop, but one background thread would remain open, still waiting for a connection. To truly stop the HttpListener, you should call Listener.Abort.

zmrcic
March 21, 2008

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

can I use simple desktop application?

Derek Smyth
October 03, 2008

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Hi Rick, just wanted to say great site filled with great information. Excellent work mate!

Vito Botta
February 05, 2009

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

I have found inspiration countless times on this blog but - my bad - I haven't yet spent a minute to write a comment. May I take this opportunity to thank you for one of the most useful websites I have ever found on these subjects!

Sanket
March 02, 2010

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Thanks, You made my day :)

Alex
November 16, 2010

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Hi, your solution does not work: try to put Thread.Sleep inside processrequest function and check it with simple load test.

Etienne
March 07, 2011

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Please note that if you use Thread.Sleep you will end up with odd behavior. If you want to do some load testing, make sure to offload the Thread.sleep to a new Thread in the ThreadPool.

Ralph Krausse
September 06, 2012

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Can this be used/configured to support external pages? If a machine didn't have IIS installed and I wanted to use this to host a site. Can that work?

thx
Ralph

Chirag Pathak
March 28, 2016

# re: Add a Web Server to your .NET 2.0 app with a few lines of code

Thanks for this great article on BeginGetContext, it really helped me a lot....

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024