Simple HTTP Server with IronPython

Serving HTTP with HttpListener

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' .

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".

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 . 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 :



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 ). Having created our server, lets build a simple way of accessing it - a dialog.