0001"""Kid-savvy HTTP Server.
0002
0003Written by Christoph Zwerschke based on CGIHTTPServer 0.4.
0004
0005This module builds on SimpleHTTPServer by implementing GET and POST
0006requests to Kid templates.
0007
0008In all cases, the implementation is intentionally naive -- all
0009requests are executed by the same process and sychronously.
0010
0011Code to create and run the server looks like this:
0012
0013 from kid.server import HTTPServer
0014 host, port = 'localhost', 8000
0015 HTTPServer((host, port)).serve_forever()
0016
0017This serves files and kid templates from the current directory
0018and any of its subdirectories.
0019
0020If you want the server to be accessible via the network,
0021use your local host name or an empty string as the host.
0022(Security warning: Don't do this unless you are inside a firewall.)
0023
0024You can also call the test() function to run the server, or run this
0025module as a script, providing host and port as command line arguments.
0026
0027The Kid templates have access to the following predefined objects:
0028
0029 FieldStorage (access to GET/POST variables)
0030 environ (CGI environment)
0031 request (the request handler object)
0032
0033Here is a simple Kid template you can use to test the server:
0034
0035 <html xmlns="http://www.w3.org/1999/xhtml"
0036 xmlns:py="http://purl.org/kid/ns#">
0037 <head><title>Python Expression Evaluator</title></head>
0038 <body>
0039 <h3 py:if="FieldStorage.has_key('expr')">
0040 ${FieldStorage.getvalue('expr')} =
0041 ${eval(FieldStorage.getvalue('expr'))}</h3>
0042 <form action="${environ['SCRIPT_NAME']}" method="post">
0043 <h3>Enter a Python expression:</h3>
0044 <input name="expr" type="text" size="40" maxlength="40" />
0045 <input type="submit" value="Submit" />
0046 </form>
0047 </body>
0048 </html>
0049"""
0050
0051__revision__ = "$Rev: 1 $"
0052__date__ = "$Date: 2005-01-01 00:00:00 +0000 (Mon, 1 Jan 2005) $"
0053__author__ = "Christoph Zwerschke (cito@online.de)"
0054__copyright__ = "Copyright 2005, Christoph Zwerschke"
0055__license__ = "MIT <http://www.opensource.org/licenses/mit-license.php>"
0056
0057
0058__all__ = ["HTTPServer", "HTTPRequestHandler"]
0059
0060import os.path
0061from urllib import unquote
0062from BaseHTTPServer import HTTPServer as BaseHTTPServer
0063from SimpleHTTPServer import SimpleHTTPRequestHandler
0064from cgi import FieldStorage
0065from kid import load_template
0066
0067
0068default_host = 'localhost'
0069default_port = 8000
0070
0071
0072class HTTPRequestHandler(SimpleHTTPRequestHandler):
0073
0074 """Complete HTTP server with GET, HEAD and POST commands.
0075 GET and HEAD also support running Kid templates.
0076 The POST command is *only* implemented for Kid templates."""
0077
0078 def do_POST(self):
0079 """Serve a POST request implemented for Kid templates."""
0080 if self.is_kid():
0081 self.run_kid()
0082 else:
0083 self.send_error(501, "Can only POST to Kid templates")
0084
0085 def send_head(self):
0086 """Version of send_head that supports Kid templates."""
0087 if self.is_kid():
0088 return self.run_kid()
0089 else:
0090 return SimpleHTTPRequestHandler.send_head(self)
0091
0092 def is_kid(self):
0093 """Test whether self.path corresponds to a Kid template.
0094
0095 The default implementation tests whether the path ends
0096 with one of the strings in the list self.kid_extensions.
0097
0098 """
0099 path = self.path
0100 i = path.rfind('?')
0101 if i >= 0:
0102 path, query = path[:i], path[i+1:]
0103 else:
0104 query = ''
0105 for x in self.kid_extensions:
0106 if path.endswith(x):
0107 self.cgi_info = path, query
0108 return True
0109 return False
0110
0111 kid_extensions = ['.kid', '.kid.html']
0112
0113 def run_kid(self):
0114 """Execute a Kid template."""
0115 scriptname, query = self.cgi_info
0116 scriptfile = self.translate_path(scriptname)
0117 if not os.path.exists(scriptfile):
0118 self.send_error(404, "No such Kid template (%r)"
0119 % scriptname)
0120 return
0121 if not os.path.isfile(scriptfile):
0122 self.send_error(403, "Kid template is not a plain file (%r)"
0123 % scriptname)
0124 return
0125
0126 env = {}
0127 env['SERVER_SOFTWARE'] = self.version_string()
0128 env['SERVER_NAME'] = self.server.server_name
0129 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
0130 env['SERVER_PROTOCOL'] = self.protocol_version
0131 env['SERVER_PORT'] = str(self.server.server_port)
0132 env['REQUEST_METHOD'] = self.command
0133 uqpath = unquote(scriptname)
0134 env['PATH_INFO'] = uqpath
0135 env['PATH_TRANSLATED'] = self.translate_path(uqpath)
0136 env['SCRIPT_NAME'] = scriptname
0137 if query:
0138 env['QUERY_STRING'] = query
0139 host = self.address_string()
0140 if host != self.client_address[0]:
0141 env['REMOTE_HOST'] = host
0142 env['REMOTE_ADDR'] = self.client_address[0]
0143 authorization = self.headers.getheader("authorization")
0144 if authorization:
0145 authorization = authorization.split()
0146 if len(authorization) == 2:
0147 import base64, binascii
0148 env['AUTH_TYPE'] = authorization[0]
0149 if authorization[0].lower() == "basic":
0150 try:
0151 authorization = base64.decodestring(authorization[1])
0152 except binascii.Error:
0153 pass
0154 else:
0155 authorization = authorization.split(':')
0156 if len(authorization) == 2:
0157 env['REMOTE_USER'] = authorization[0]
0158 if self.headers.typeheader is None:
0159 env['CONTENT_TYPE'] = self.headers.type
0160 else:
0161 env['CONTENT_TYPE'] = self.headers.typeheader
0162 length = self.headers.getheader('content-length')
0163 if length:
0164 env['CONTENT_LENGTH'] = length
0165 accept = []
0166 for line in self.headers.getallmatchingheaders('accept'):
0167 if line[:1] in "\t\n\r ":
0168 accept.append(line.strip())
0169 else:
0170 accept = accept + line[7:].split(',')
0171 env['HTTP_ACCEPT'] = ','.join(accept)
0172 ua = self.headers.getheader('user-agent')
0173 if ua:
0174 env['HTTP_USER_AGENT'] = ua
0175 co = filter(None, self.headers.getheaders('cookie'))
0176 if co:
0177 env['HTTP_COOKIE'] = ', '.join(co)
0178
0179 self.send_response(200, "Script output follows")
0180
0181
0182 try:
0183 template_module = load_template(scriptfile, cache=1)
0184 template = template_module.Template(
0185 request=self, environ=env,
0186 FieldStorage=FieldStorage(self.rfile, environ=env))
0187 s = str(template)
0188 self.send_header("Content-type", "text/html")
0189 self.send_header("Content-Length", str(len(s)))
0190 self.end_headers()
0191 self.wfile.write(s)
0192 except Exception, e:
0193 self.log_error("Kid template exception: %s", str(e))
0194 else:
0195 self.log_message("Kid template exited OK")
0196
0197
0198class HTTPServer(BaseHTTPServer):
0199
0200 def __init__(self,
0201 server_address = None,
0202 RequestHandlerClass = HTTPRequestHandler):
0203 if server_address is None:
0204 server_address = (default_host, default_port)
0205 BaseHTTPServer.__init__(self,
0206 server_address, HTTPRequestHandler)
0207
0208
0209def test(server_address = None,
0210 HandlerClass = HTTPRequestHandler,
0211 ServerClass = HTTPServer,
0212 protocol = "HTTP/1.0"):
0213 """Test the HTTP request handler class."""
0214
0215 HandlerClass.protocol_version = protocol
0216 server = ServerClass(server_address, HandlerClass)
0217 sa = server.socket.getsockname()
0218 print "Serving HTTP on", sa[0], "port", sa[1], "..."
0219 server.serve_forever()
0220
0221
0222def main():
0223 """This runs the Kid-savvy HTTP server.
0224
0225 Provide host and port as command line arguments.
0226 The current directory serves as the root directory.
0227
0228 """
0229
0230 from sys import argv, exit
0231
0232 if len(argv) > 3:
0233 print "Usage:", argv[0], "[host]:[port]"
0234 exit(2)
0235
0236 if len(argv) < 2:
0237 server_address = (default_host, default_port)
0238 else:
0239 if len(argv) == 3:
0240 host = argv[1]
0241 port = argv[2]
0242 else:
0243 host = argv[1].split(':', 1)
0244 if len(host) < 2:
0245 host = host[0]
0246 if host.isdigit():
0247 port = host
0248 host = ''
0249 else:
0250 port = None
0251 else:
0252 host, port = host
0253 if port:
0254 if port.isdigit():
0255 port = int(port)
0256 else:
0257 print "Bad port number."
0258 exit(1)
0259 else:
0260 port = default_port
0261 server_address = (host, port)
0262
0263 test(server_address)
0264
0265
0266if __name__ == '__main__':
0267 main()