bundled/webpy/web/httpserver.py @ 001f5ecfd9bc

Merge the webui skipped line counts -- too important to leave in the branch.
author Steve Losh <steve@stevelosh.com>
date Wed, 21 Oct 2009 19:58:13 -0400
parents 7c9d165e0cd1
children (none)
__all__ = ["runsimple"]

import sys, os
import webapi as web
import net
import utils

def runbasic(func, server_address=("0.0.0.0", 8080)):
    """
    Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` 
    is hosted statically.

    Based on [WsgiServer][ws] from [Colin Stewart][cs].
    
  [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
  [cs]: http://www.owlfish.com/
    """
    # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
    # Modified somewhat for simplicity
    # Used under the modified BSD license:
    # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5

    import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
    import socket, errno
    import traceback

    class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
        def run_wsgi_app(self):
            protocol, host, path, parameters, query, fragment = \
                urlparse.urlparse('http://dummyhost%s' % self.path)

            # we only use path, query
            env = {'wsgi.version': (1, 0)
                   ,'wsgi.url_scheme': 'http'
                   ,'wsgi.input': self.rfile
                   ,'wsgi.errors': sys.stderr
                   ,'wsgi.multithread': 1
                   ,'wsgi.multiprocess': 0
                   ,'wsgi.run_once': 0
                   ,'REQUEST_METHOD': self.command
                   ,'REQUEST_URI': self.path
                   ,'PATH_INFO': path
                   ,'QUERY_STRING': query
                   ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
                   ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
                   ,'REMOTE_ADDR': self.client_address[0]
                   ,'SERVER_NAME': self.server.server_address[0]
                   ,'SERVER_PORT': str(self.server.server_address[1])
                   ,'SERVER_PROTOCOL': self.request_version
                   }

            for http_header, http_value in self.headers.items():
                env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
                    http_value

            # Setup the state
            self.wsgi_sent_headers = 0
            self.wsgi_headers = []

            try:
                # We have there environment, now invoke the application
                result = self.server.app(env, self.wsgi_start_response)
                try:
                    try:
                        for data in result:
                            if data: 
                                self.wsgi_write_data(data)
                    finally:
                        if hasattr(result, 'close'): 
                            result.close()
                except socket.error, socket_err:
                    # Catch common network errors and suppress them
                    if (socket_err.args[0] in \
                       (errno.ECONNABORTED, errno.EPIPE)): 
                        return
                except socket.timeout, socket_timeout: 
                    return
            except:
                print >> web.debug, traceback.format_exc(),

            if (not self.wsgi_sent_headers):
                # We must write out something!
                self.wsgi_write_data(" ")
            return

        do_POST = run_wsgi_app
        do_PUT = run_wsgi_app
        do_DELETE = run_wsgi_app

        def do_GET(self):
            if self.path.startswith('/static/'):
                SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
            else:
                self.run_wsgi_app()

        def wsgi_start_response(self, response_status, response_headers, 
                              exc_info=None):
            if (self.wsgi_sent_headers):
                raise Exception \
                      ("Headers already sent and start_response called again!")
            # Should really take a copy to avoid changes in the application....
            self.wsgi_headers = (response_status, response_headers)
            return self.wsgi_write_data

        def wsgi_write_data(self, data):
            if (not self.wsgi_sent_headers):
                status, headers = self.wsgi_headers
                # Need to send header prior to data
                status_code = status[:status.find(' ')]
                status_msg = status[status.find(' ') + 1:]
                self.send_response(int(status_code), status_msg)
                for header, value in headers:
                    self.send_header(header, value)
                self.end_headers()
                self.wsgi_sent_headers = 1
            # Send the data
            self.wfile.write(data)

    class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
        def __init__(self, func, server_address):
            BaseHTTPServer.HTTPServer.__init__(self, 
                                               server_address, 
                                               WSGIHandler)
            self.app = func
            self.serverShuttingDown = 0

    print "http://%s:%d/" % server_address
    WSGIServer(func, server_address).serve_forever()

def runsimple(func, server_address=("0.0.0.0", 8080)):
    """
    Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. 
    The directory `static/` is hosted statically.

    [cp]: http://www.cherrypy.org
    """
    from wsgiserver import CherryPyWSGIServer
    from SimpleHTTPServer import SimpleHTTPRequestHandler
    from BaseHTTPServer import BaseHTTPRequestHandler

    class StaticApp(SimpleHTTPRequestHandler):
        """WSGI application for serving static files."""
        def __init__(self, environ, start_response):
            self.headers = []
            self.environ = environ
            self.start_response = start_response

        def send_response(self, status, msg=""):
            self.status = str(status) + " " + msg

        def send_header(self, name, value):
            self.headers.append((name, value))

        def end_headers(self):
            pass

        def log_message(*a): pass

        def __iter__(self):
            environ = self.environ

            self.path = environ.get('PATH_INFO', '')
            self.client_address = environ.get('REMOTE_ADDR','-'), \
                                  environ.get('REMOTE_PORT','-')
            self.command = environ.get('REQUEST_METHOD', '-')

            from cStringIO import StringIO
            self.wfile = StringIO() # for capturing error

            f = self.send_head()
            self.start_response(self.status, self.headers)

            if f:
                block_size = 16 * 1024
                while True:
                    buf = f.read(block_size)
                    if not buf:
                        break
                    yield buf
                f.close()
            else:
                value = self.wfile.getvalue()
                yield value
                    
    class WSGIWrapper(BaseHTTPRequestHandler):
        """WSGI wrapper for logging the status and serving static files."""
        def __init__(self, app):
            self.app = app
            self.format = '%s - - [%s] "%s %s %s" - %s'

        def __call__(self, environ, start_response):
            def xstart_response(status, response_headers, *args):
                write = start_response(status, response_headers, *args)
                self.log(status, environ)
                return write

            path = environ.get('PATH_INFO', '')
            if path.startswith('/static/'):
                return StaticApp(environ, xstart_response)
            else:
                return self.app(environ, xstart_response)

        def log(self, status, environ):
            outfile = environ.get('wsgi.errors', web.debug)
            req = environ.get('PATH_INFO', '_')
            protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
            method = environ.get('REQUEST_METHOD', '-')
            host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), 
                              environ.get('REMOTE_PORT','-'))

            #@@ It is really bad to extend from 
            #@@ BaseHTTPRequestHandler just for this method
            time = self.log_date_time_string()

            msg = self.format % (host, time, protocol, method, req, status)
            print >> outfile, utils.safestr(msg)
            
    func = WSGIWrapper(func)
    server = CherryPyWSGIServer(server_address, func, server_name="localhost")

    print "http://%s:%d/" % server_address
    try:
        server.start()
    except KeyboardInterrupt:
        server.stop()