So I’m in the process of doing a refactor of some code and I thought I’d share a little on where it is going.
I’m morphing some code into a server that serves requests from a web browser. The idea being that it is a “client” but you access your UI from a web browser.
This is pretty easily done, as I’m writing this from Python to Python. As a matter of fact, the “server” portion looks something like this:
import sys, mymod
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
gh = mymod.Get( self )
return gh.process()
def do_POST(self):
ph = mymod.Post( self )
ph.process()
def main():
try:
server = HTTPServer(('127.0.0.1', 5000), MyHandler)
print 'started httpserver...' + str(server.server_address)
server.serve_forever()
except KeyboardInterrupt:
print 'Error: shutting down server' + str( sys.exc_info() )
server.socket.close()
if __name__ == '__main__':
main()
So as you can see, I’ve delegated GET and POST handling to their respective Handler classes. (forgive me for the double usage of the term “handler” as it is also used in the HTTPRequestHandler context as well)
I suppose that typically, a first cut would be that you would have a large if/else statement in each of do_GET/do_POST that would handle each of your “actions” for you.
Perhaps something like:
if ( action == "foo.html"):
self.rwrite( open( 'foo.html').read() )
elsif ( action == "bar.html"):
self.rwrite( open('bar.html').read() )
.
.
.
I can only hope that your gut reaction to this would be “Blech, Burp, Blah!”. The next logical step would be to take each of the bits of code and put them into a method, giving you something similar to:
if ( action == "foo.html"):
self.rwrite( self.generateFooContent() )
elsif ( action == "bar.html"):
self.rwrite( self.generateBarContent() )
.
.
.
Cleaner for sure, but in essence the same problem which should garner a similar, perhaps less offensive reaction as the previous example.
Anyway, I thought that a crafty solution would be to have a generic dispatcher method, that would take an “action” as an argument, and then instantiate a class of the same name, and then run a process() method that would then be responsible for generating the appropriate content based on POST/GET and action “/foo”, “/bar”
The example within this blog entry is the idea of a “signin” action. You’d go to http://localhost/signin where you’d be presented with an HTML form where you would enter in your username and password, and then submit for authentication.
So my solution would say, you’ll want a Signin object (two actually) for your GET request and your POST request. The system as it will be written will take your action from the HTTP path, do a simple parse (just whack off the leading /) and then auto instantiate the appropriate get/post Signin object and call a common method, which returns the results which are then rendered by your webbrowser.
When you’re ready to add another action (say /logout), all you need to do is write a logout.html page and one or both of a Logout object in get.py and/or post.py
Make sense? How about we roll up the sleeves.
The layout consists of a Handler super class. This is subclassed by Get and Post.
Additionally, we have an Action superclass which all of our GET and POST action classes will subclass.
It may help to remember that any non .html request served will have a GET class to actually display the content, a POST class to actually process items from the form, and naturally a content file that any UI/form items can be defined.
In the below code, there is the idea of a “signin” action, the idea of a user authenticating. Thinking of this at a high level, you’re going to want an HTML file that has your FORM definition, and any other help links, help documentation, or any other site header/footer items here.
As mentioned above, you’d then want a Signin object that the GET class can call to display up this information. Finally, you’d want another Signin object that the POST class will all to process and also display your results.
I could be argued that you’d only want one Signin class that would handle both GET/POST items. I suppose that could be just as valid of a solution. The main thing to watch out for in either implementation is that you are consistent, so you don’t run into any unforeseen issues because you decided to sway from your implementation ideals.
I chose this way mainly so that I knew when I was modifying the get.py module I *knew* that I was only concerned with serving up raw content and not having to worry about any real processing. (Again, if you had your FORM’s doing GETs instead of POSTs then you’re down to a single class anyway). Likewise, when I am modifying the post.py module I know that some real work is getting done in each respective process() method, and I don’t have to worry about any details that could concern GET processing.
It’s up to you on which way (or some other not mentioned here) way you implment. My big concern is that no matter what you do, be consistent.
Ok then. Where’s this code anyway? I’ll blat it out and then talk a bit at the end.
handler.py:
class Handler ( MyBaseObject ):
handler = None
"""
Valid content types that we'll serve up.
"""
content_types = { "html": "text/html",
"css" : "text/css",
"ico" : "image/x-icon"
}
def __init__ (self, h ):
mymod.MyModObject.__init__( self )
self.handler = h
def __getattr__ ( self , name ):
return getattr( self.handler, name )
def delegateGetAction( self, action ):
return self.delegate( 'mymod.get.' + action , None)
def delegatePostAction( self, action, params ):
return self.delegate( 'mymod.post.' + action, params )
def delegate( self, handlerName, params ):
try:
h = eval( handlerName )( self. self.getBaseDir(), self.getContentDir() )
return h.process( params )
except:
self.exception("issue with delegating " + handlerName )
h = eval( "mymod.get.Default")(self. self.getBaseDir(), self.getContentDir() )
return h.process( sys.exc_info() )
def send200Header( self ):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
"""
path should contain something like [full or partial path].[extension]
We'll split on . and then use to grab out of self.content_types
If we don’t have a definition return text/html
“”"
def getContentType( self, path ):
try:
return self.content_types[ path.split(”.”)[-1] ]
except:
return self.content_types[”html”]
“”"
This just spits out our HTML header
“”"
def generateHeader( self ):
return open( self.getContentDir() + “header.html” ).read()
“”"
This generates our full HTML page. Takes care of calling Header/Footer
“”"
def generateResponse( self, content ):
self.send200Header()
self.wfile.write( self.generateHeader() )
self.wfile.write( content )
self.wfile.write( self.generateFooter() )
“”"
This spits out our HTML Footer
“”"
def generateFooter( self ):
return open( self.getContentDir() + “footer.html” ).read()
action.py:
“”"
Super of all action/ classes
December 2005
“”"
import os, mymod
class Action ( mymod.MyModObject ):
content_dir =”"
base_dir = “”
errors = []
def __init__ (self, bd, cd ):
mymod.MyModObject.__init__( self )
self.content_dir = cd
self.base_dir = bd
def addError( self, errMesg ):
self.errors.append( errMesg )
def generateErrorMessage( self ):
rv = “”
for e in self.errors :
rv += “<li>” + e + “</li>”
return self.getErrorHeader() + rv + self.getErrorFooter()
def getErrorHeader( self ):
return ‘<center><table class=”error”><tr><td>The following errors were encountered: <ul>’
def getErrorFooter( self ):
return “</ul></td></tr></table></center>”
get.py:
class Get ( mymod.Handler ):
def __init__ (self, h ):
mymod.Handler.__init__( self, h )
def process(self):
try:
if “.css” in self.path or “.ico” in self.path or “.jpg” in self.path:
f = open( self.getBaseDir() + self.path)
self.send_response(200)
self.send_header(’content-type’, self.getContentType( self.path ) )
self.wfile.write(f.read())
f.close()
return
self.generateResponse( self.delegateGetAction( self.path[1:].capitalize() ) )
return
except IOError:
self.handler.send_error(404,’File Not Found: %s’ % self.path)
“”"
What we serve as our default content
“”"
class Default( mymod.Action ):
def __init__( self, bd, cd ):
mymod.Action.__init__( self, bd,cd)
def process( self, params ):
return open( self.content_dir + “default.html” ).read()
“”"
localhost/signin processing
Displays HTML form to sign in to the mymod service
“”"
class Signin (mymod.Action ):
def __init__(self, bd, cd ):
mymod.Action.__init__( self , bd, cd )
def process (self, params ):
self.debug( str(self) + “: Processing Signin stuffs ” )
return open( self.content_dir + “signin.html” ).read()
post.py:
import cgi,sys,os,mymod, mymod.util
“”"
Post
This is where all of our POST queries will end up.
Each action that is possible will be handled by an appropriate POST Action class
For instance, http://localhost/signin/ will be handled by action/post/signin class
“”"
class Post(mymod.Handler):
def __init__ (self, h ):
mymod.Handler.__init__( self, h )
def process( self ):
try:
(ctype,pdict) = cgi.parse_header(self.headers.getheader(’content-type’))
query = cgi.parse_multipart(self.rfile, pdict)
self.generateResponse( self.delegatePostAction( self.path[1:].capitalize(), query ) )
except :
self.exception( “Error processing ” + self.path[1:] )
class Signin (mymod.Action):
userName = “”
password = “”
def __init__(self, bd, cd):
mymod.Action.__init__( self, bd, cd)
def process (self, params ):
self.debug( str(self) + ” processing signin”)
self.userName = params.get(”username”)[0]
self.password = params.get(”password”)[0]
“”"
do your authentication stuff here
“”"
if you are logged in
return “we’re logged in”
else
return “you aren’t logged in”
You can ignore Handler because this is just common items that are needed in both Get and Post. Same thing goes for Action, just a super for all of our “action” classes.
One thing to note is that all of our Action classes have a process() method. This is important because our delegator methods will call this method when getting processed. AFAIK you can’t really enforce an interface in Python, so again be consistent and keep these things in mind.
Let’s take a trip through how a request is processed. You go to http://localhost:5000/signin and you’re presented with a form. First our server code senses a GET request and first calls the do_GET() method from the MyHandler class (see very top of this blog entry). We then instantiate our Get class and call our process() method. Get.process() is a little special in that we can also do some “default” processing of .css, .html, or .ico files so we can just serve raw HTML or load our favicon and stylesheets. If this is the case, we spit the content back out (setting content-type appropriately) and we’re done. However if that isn’t the case, we strip our leading ‘/” from the path and then fire up whatever object that our “path” is set to. Notice if we try to fire off something that doesn’t exist (ie: we don’t have any appropriate action classes) we serve up our default page as a safety net. This means if you tried to do http://localhost:5000/foobar you’d just get our default.html stuff.
The beauty starts to shine here, because now all you have to do to accomodate additional actions is to create your .html content file, and then add like named action objects to get.py/post.py (I know, I’m repeating myself here).
Seem cool? I hope so, because I think it’s pretty cool.
Now, after all of this has been said, I have a question. There inside handler.py is our delegate method. It does something like:
h = eval( handlerName )( self.getBaseDir(), self.getContentDir() )
What I want to know from those of you who are much better at Python than I is this. Is this the best way of doing this? Is there a better way to call an object “dynamically” so to speak without having to actually code the name of the object?
See I want this bit so that I don’t end up having some nasty huge if/else statement that I have to continually update whenever I have new actions that I want to add (or take away old ones).
If you’ve got an answer, click that contact link above and send off an email.