Over the weekend of August 3rd 2002, I attended the Pacific Northwest Software Symposium, presented by the Complete Programmers Network. While there, I heard Dave Thomas of the Pragmatic Programmers deliver a talk on Ruby. He alluded to the Ruby class he gives in which he spends 5 hours teaching about Ruby syntax and libraries, and then gives the students an hour to write a webserver that operates on sockets (i.e. no cheating and using the built in webserver library). So I thought to myself "how fast could I write a simple web server in python?" By the end of the next conference session, I had the answer...

P.S. I'd love feed back on any areas where I could have made better use of python's features or phrased things more "pythonicly".

code

#
# ws30 -- the thirty minute web server
# author: Wilhelm Fitzpatrick (rafial@well.com)
# date: August 3rd, 2002
# version: 1.0
#
# Written after attending a Dave Thomas talk at PNSS and hearing about
# his "write a web server in Ruby in one hour" challenge.
#
# Actual time spent:
#  30 minutes reading socket man page
#  30 minutes coding to first page fetched
#   3 hours making it prettier & more pythonic

import os, socket, sys

defaults = [ '127.0.0.1', '8080' ]
mimeTypes = { '.jpg' : 'image/jpg', '.gif' : 'image/gif', '.png' : 'image/png',
              '.html' : 'text/html', '.pdf' : 'application/pdf' }
response = {}

response[200] =\
"""HTTP/1.0 200 Okay
Server: ws30
Content-type: %s

%s
"""

response[301] =\
"""HTTP/1.0 301 Moved
Server: ws30
Content-type: text/plain
Location: %s

moved
"""

response[404] =\
"""HTTP/1.0 404 Not Found
Server: ws30
Content-type: text/plain

%s not found
"""

directoryListing =\
"""<html>
<head><title>%s</title></head>
<body>
<a href="%s..">..</a><br>
%s
</body>
</html>
"""

directoryLine = '<a href="%s">%s</a><br>'

def serverSocket( host, port ):
    s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    s.bind( ( host, port ) )
    s.listen( 1 )
    return s

def listen( s ):
    connection, client = s.accept()
    return connection.makefile( 'r+' )

def getRequest( stream ):
    method = None
    while 1:
        line = stream.readline()
        if not line.strip(): break
        elif not method: method, uri, protocol = line.split()
    return uri

def listDirectory( uri ):
    entries = os.listdir( '.' + uri )
    entries.sort()
    return directoryListing % ( uri, uri, '\n'.join(
        [directoryLine % ( e, e ) for e in entries] ) )

def getFile( path ):
    f = open( path )
    try: return f.read()
    finally: f.close()

def getContent( uri ):
    print 'fetching:', uri
    try:
        path = '.' + uri
        if os.path.isfile( path ):
            return ( 200, getMime( uri ), getFile( path ) )
        if os.path.isdir( path ):
            if( uri.endswith( '/' ) ):
                return ( 200, 'text/html', listDirectory( uri ) )
            else:
                return ( 301, uri + '/' )
        else: return ( 404, uri )
    except IOError, e:
        return ( 404, e )

def getMime( uri ):
    return mimeTypes.get( os.path.splitext( uri )[1], 'text/plain' )

def sendResponse( stream, content ):
    stream.write( response[content[0]] % content[1:] )

if __name__ == '__main__':
    args, nargs = sys.argv[1:], len( sys.argv ) - 1
    host, port = ( args + defaults[-2 + nargs:] )[0:2]
    server = serverSocket( host, int(port) )
    print 'starting %s on %s...' % ( host, port )
    try:
        while 1:
            stream = listen ( server )
            sendResponse( stream, getContent( getRequest( stream ) ) )
            stream.close()
    except KeyboardInterrupt:
        print 'shutting down...'
    server.close()

comments on the code

ThirtyMinuteWebServer (last edited 2008-03-04 08:33:24 by localhost)