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        # Execute template in this process
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()