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()