Simple HTTP Server with IronPython

Serving HTTP with HttpListener

Lightning strike - must have some kind of connection to this article... Hmmm...

Follow my exploration of living a spiritual life and finding the kingdom at Unpolished Musings.

Introduction

The Python standard library includes several simple classes, like BaseHTTPServer for serving over HTTP. These can be very useful for simple proof-of-concept implementations that present information via a web-browser.

This article implements a (very) simple server in IronPython, which serves over HTTP. It doesn't serve files from a directory structure, but is easy to extend to perform whatever task you want.

As well as using the HttpListener class, this article explores text encoding, asynchronous callbacks, URI and XHTML escaping, the system message box, and creating a simple Windows Forms dialog. If you are new to .NET, this is a valuable tour of parts of the .NET 'standard library' [1]. Cool

HttpListener

The basic class for listening and responding to HTTP requests, is HttpListener.

Note

This class is available only on computers running the Windows XP SP2 or Windows Server 2003 operating systems. If you attempt to create an HttpListener object on a computer that is running an earlier operating system, the constructor throws a PlatformNotSupportedException exception.

This class probably won't scale to writing a production webserver or application server (at least, why would you want to?). It is fine for simple servers though, and can handle client authentication and HTTPS.

There are two ways of using this class, synchronous and asynchronous. In synchronous mode, the listener class blocks whilst handling each request (like SimpleHTTPServer and friends). In asynchronous mode, each request is handled in its own thread (with all the associated complexity of threads if you are accessing shared data structures).

This article will use HttpListener in asynchronous mode. The basic use pattern is very simple. We need to use the AsyncCallback delegate to create the callback which will be launched to handle each request.

from System import AsyncCallback
from System.Net import HttpListener, HttpListenerException

listener = HttpListener()
prefix = 'http://*:8080/'
listener.Prefixes.Add(prefix)
try:
    listener.Start()
except HttpListenerException:
    raise Exception('Starting server failed')

result = listener.BeginGetContext(AsyncCallback(handleRequest), listener)
result.AsyncWaitHandle.WaitOne()

listener.Close()

You specify the port to listen on (as well as the domain to handle requests for), using prefixes. The 'Prefixes' property of the listener is a collection, so one listener can listen to as many of these as you want. A prefix string is a scheme (http or https), a host, an optional port, and an optional path. An example of a complete prefix string is "http://localhost:8080/customerData/". When you specify an explicit port, you can replace the domain name with a "*" to handle requests to all domains.

The listener is started by calling Start(), which can raise a System.Net.HttpListenerException if the port is already in use (or there is some other problem).

We start the handling of requests, by calling BeginGetContext, this requires an asynchronous callback - so we use the AsyncCallback delegate which can wrap an IronPython function.

The code above then waits for a request to arrive and then closes the listener. To serve continuously, we can use:

while True:
    result = listener.BeginGetContext(AsyncCallback(handleRequest), listener)
    result.AsyncWaitHandle.WaitOne()

Here result is an instance of the AsyncResult Class, which "Encapsulates the results of an asynchronous operation on an asynchronous delegate". Smile

Handling Requests

The actual request handling is done in the function that we passed to the AsyncCallback delegate.

from System.Text import Encoding

def handleRequest(result):
    listener = result.AsyncState
    context = listener.EndGetContext(result)

    request = context.Request
    response = context.Response
    text = getTextFromRequest(request)
    buffer = Encoding.UTF8.GetBytes(text)
    response.ContentLength64 = buffer.Length
    output = response.OutputStream
    output.Write(buffer, 0, buffer.Length)
    output.Close()

This should be a function that takes one argument, the AsyncResult object that we saw earlier. This allows us to get back to the listener instance and call EndGetContext, which releases the listener to receive an other request.

From the context (an HttpListenerContext) we have access to objects representing the request, and the response. On the response we set the content length (ContentLength64) and have access to a stream (OutputStream) to write the output to (which must be closed when we have finished writing to it).

If we want to send text from a string, we'll have to convert the string into bytes first. We can do this with Encoding.UTF8.GetBytes [2]. The Encoding class is a very useful one for converting between text and bytes on .NET. GetBytes returns a bytes buffer, which is exactly what is needed by the Write method of the output stream.

SimpleServer

Using all of this, we can put together a simple server class:

from System import AsyncCallback

from System.Net import HttpListener, HttpListenerException
from System.Text import Encoding


class SimpleServer(object):
    def __init__(self):
        self.text = """
        <HTML>
        <HEAD><TITLE>Welcome to the Simple Server</TITLE></HEAD>
        <BODY><STRONG><H1>Welcome to the Simple Server</H1>%s</STRONG></BODY>
        </HTML>
        """

        self.pagesServed = 0


    def serveforever(self, port):
        self.failed = False
        listener = HttpListener()
        prefix = 'http://*:%s/' % str(port)
        listener.Prefixes.Add(prefix)
        try:
            listener.Start()
        except HttpListenerException:
            self.failed = True
            return

        while True:
            result = listener.BeginGetContext(AsyncCallback(self.handleRequest), listener)
            result.AsyncWaitHandle.WaitOne()


    def handleRequest(self, result):
        listener = result.AsyncState
        try:
            context = listener.EndGetContext(result)
        except:
            # Catch the exception when the thread has been aborted
            return
        request = context.Request
        response = context.Response
        text = self.getText(request)
        buffer = Encoding.UTF8.GetBytes(text)
        response.ContentLength64 = buffer.Length
        output = response.OutputStream
        output.Write(buffer, 0, buffer.Length)
        output.Close()


    def getText(self, request):
        self.pagesServed += 1
        url = '<P><STRONG>URL Requested: %s</STRONG></P>' % request.RawUrl
        pagesServed = '<P><STRONG>Number of Pages Served: %s</STRONG></P>' % self.pagesServed
        return self.text % (url + pagesServed)

This serves a simple HTML page, which reports the URL requested, and the number of pages served so far. In order to illustrate it, the next section will create a Windows Forms dialog which launches the server on a separate thread. It closes the server by aborting the thread (naughty), which means we need to wrap the call to EndGetContext in a try... except block, because the last call could happen after the listener has been closed when the main thread exits.

We get the URL that has been requested from the request object, using the request.RawUrl. To build more complex behaviour, look at the properties and methods available on the request and response objects.

For handling URLs, you may find the Uri class useful. It "provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI" (including escaping and unescaping [3]). Smile

Having created our server, lets build a simple way of accessing it - a dialog.

Server Dialog

This dialog presents you with a textbox to enter a port number, and a button to launch the server.

The server is launched on its own thread, which is aborted when you close the form. If the port number is invalid, or the server fails to start (port in use or some other problem) then a message box appears warning you about the problem.

import clr
clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')

from SimpleServer import SimpleServer

from System.Drawing import Point, Size
from System.Threading import Thread, ThreadStart
from System.Windows.Forms import (
    Application, Button, Form,
    FormBorderStyle, Label,
    MessageBox, MessageBoxButtons,
    MessageBoxIcon, TextBox
)

class ServerDialog(Form):

    def __init__(self):
        self.layout()

        self.server = SimpleServer()
        self.serverThread = None

        self.serveButton.Click += lambda _, __: self.serve()
        self.Closing += lambda _, __: self.onClose()


    def layout(self):
        self.portLabel = Label()
        self.portTextBox = TextBox()
        self.serveButton = Button()

        self.portLabel.AutoSize = True
        self.portLabel.Location = Point(13, 22)
        self.portLabel.Size = Size(29, 13)
        self.portLabel.Text = 'Port:'

        self.portTextBox.Location = Point(49, 22)
        self.portTextBox.Size = Size(42, 20)
        self.portTextBox.Text = '8080'

        self.serveButton.Location = Point(49, 60)
        self.serveButton.Size = Size(75, 23)
        self.serveButton.Text = 'Serve'

        self.ClientSize = Size(190, 108)
        self.Controls.Add(self.serveButton)
        self.Controls.Add(self.portTextBox)
        self.Controls.Add(self.portLabel)
        self.FormBorderStyle = FormBorderStyle.FixedDialog
        self.Text = 'Simple Server'


    def onClose(self):
        if self.serverThread is not None:
            self.serverThread.Abort()


    def serve(self):
        port = self.portTextBox.Text.strip()
        if not port.isdigit() or int(port) < 80 or int(port) > 65535:
            MessageBox.Show(
                'The Port Number Must Be an Integer > 79 and < 65536',
                "Error Starting Server",
                MessageBoxButtons.OK,
                MessageBoxIcon.Error
            )
            return

        print 'Starting server on port: %s' % port
        self.serverThread = Thread(ThreadStart(lambda : self.server.serveforever(port)))
        self.serverThread.Start()
        Thread.Sleep(50)
        if self.server.failed:
            MessageBox.Show(
                'Failed to Start Server: Please Check that the Port is Not in Use',
                "Error Starting Server",
                MessageBoxButtons.OK,
                MessageBoxIcon.Error
            )
        else:
            self.serveButton.Enabled = False
            self.portTextBox.Enabled = False


Application.Run(ServerDialog())

When you launch this application, it will look like this:

The Server Dialog before pressing Serve

As soon as you press 'Serve' (assuming the server starts successfully of course), the text box and serve button will be disabled and you should be able to browse to http://localhost:8080/ (or whatever port you chose) and see the results:

Simple Server - Serving

Have fun. Very Happy

[1]Translating these examples back into C# (for those not yet drinking the IronPython Kool-Aid) would be very easy, but you have quite a few type declarations to add. Wink
[2]UTF8 is a specific encoding instance. Needless to say there are others...
[3]For escaping text for inclusion in HTML (well, XHTML really), you can use System.Security.SecurityElement, specifically the Escape method.

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Fri Nov 27 18:32:35 2009.

Counter...