"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/qp/hub/web.py $
$Id: web.py 28677 2006-08-31 15:48:07Z dbinger $

Provides a HubServer-based HTTP/SCGI server.
"""
from BaseHTTPServer import BaseHTTPRequestHandler
from qp.hub.dispatcher import HubServer
from qp.lib.site import Site
from qp.pub.common import get_publisher, clear_publisher
from urllib import unquote
from signal import signal, SIGHUP
import os
import qp
import socket
import sys

class HTTPRequestHandler(BaseHTTPRequestHandler):

    server_version = "qp/%s" % qp.__version__

    def __init__(self, connection, address, info, **kwargs):
        self.required_cgi_environment = dict(
            SERVER_SOFTWARE=self.server_version,
            GATEWAY_INTERFACE='CGI/1.1')
        self.required_cgi_environment.update(kwargs)
        BaseHTTPRequestHandler.__init__(
            self, connection, address, info)

    def get_cgi_env(self, method):
        env = dict(
            SERVER_NAME=self.server.server_name,
            SERVER_PROTOCOL=self.protocol_version,
            SERVER_PORT=str(self.server.server_port),
            REQUEST_METHOD=method,
            REMOTE_ADDR=self.client_address[0],
            REMOTE_PORT=self.client_address[1])
        if '?' in self.path:
            env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?', 1)
        else:
            env['PATH_INFO'] = self.path
        env['PATH_INFO'] = unquote(env['PATH_INFO'])
        script_name = self.required_cgi_environment.get('SCRIPT_NAME', '')
        if script_name:
            path_info = env['PATH_INFO']
            if path_info.startswith(script_name): # and it better!
                env['PATH_INFO'] = path_info[len(script_name):]
            else:
                print
                print "All paths are expected to start with the script_name."
                print "  The script_name is %r." % script_name
                print "  The requested path is %r." % self.path
                print
        if self.headers.typeheader is None:
            env['CONTENT_TYPE'] = self.headers.type
        else:
            env['CONTENT_TYPE'] = self.headers.typeheader
        env['CONTENT_LENGTH'] = self.headers.getheader(
            'content-length') or "0"
        for name, value in self.headers.items():
            header_name = 'HTTP_' + name.upper().replace('-', '_')
            env[header_name] = value
        accept = []
        for line in self.headers.getallmatchingheaders('accept'):
            if line[:1] in "\t\n\r ":
                accept.append(line.strip())
            else:
                accept = accept + line[7:].split(',')
        env['HTTP_ACCEPT'] = ','.join(accept)
        co = filter(None, self.headers.getheaders('cookie'))
        if co:
            env['HTTP_COOKIE'] = ', '.join(co)
        env.update(self.required_cgi_environment)
        return env

    def process(self, env):
        hit = get_publisher().process(self.rfile, env)
        response = hit.get_response()
        try:
            self.send_response(*response.get_status())
            response.write(self.wfile, include_status=False,
                           include_body=(self.command!='HEAD'))
        except (IOError, socket.error), err:
            print "Error while sending response ignored: %s" % err

    def handle_one_request(self):
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            self.close_connection = 1
            return
        if not self.parse_request():
            return
        return self.process(self.get_cgi_env(self.command))

    def log_request(self, *args, **kwargs):
        pass

    def send_response(self, code, message=None):
        """
        Copied, with regret, from BaseHTTPRequestHandler, except that the line
        that adds the 'Date' header is removed.
        """
        self.log_request(code)
        if message is None:
            if code in self.responses:
                message = self.responses[code][0]
            else:
                message = ''
        if self.request_version != 'HTTP/0.9':
            self.wfile.write("%s %d %s\r\n" %
                             (self.protocol_version, code, message))
        self.send_header('Server', self.version_string())


# netstring utility functions
def ns_read_size(input):
    size = ""
    while 1:
        c = input.read(1)
        if c == ':':
            break
        elif not c:
            if not size:
                return None
            raise IOError, 'short netstring read'
        size = size + c
    return long(size)

def ns_reads(input):
    size = ns_read_size(input)
    if size is None:
        return None
    data = ""
    while size > 0:
        s = input.read(size)
        if not s:
            raise IOError, 'short netstring read'
        data = data + s
        size -= len(s)
    if input.read(1) != ',':
        raise IOError, 'missing netstring terminator'
    return data

def read_env(input):
    headers = ns_reads(input)
    if headers is None:
        return None
    items = headers.split("\0")
    items = items[:-1]
    assert len(items) % 2 == 0, "malformed headers"
    env = {}
    for i in range(0, len(items), 2):
        env[items[i]] = items[i+1]
    return env


class SCGIHandler(object):

    def __init__(self, script_name=''):
        self.script_name = script_name

    def handle_connection(self, conn):
        input = conn.makefile("r")
        output = conn.makefile("w")
        env = read_env(input)
        if env is None:
            # Nothing here.
            return
        if (env.get('SCRIPT_NAME') and
            env.get('SCRIPT_NAME') == env.get('REQUEST_URI') and
            env.get('SCRIPT_NAME').startswith(self.script_name) and
            env.get('PATH_INFO') is None):
            # This looks like it is coming through mod_scgi and
            # needs repair.
            env['PATH_INFO'] = env['SCRIPT_NAME'][len(self.script_name):]
            env['SCRIPT_NAME'] = self.script_name
            assert env['SCRIPT_NAME'] + env['PATH_INFO'] == env['REQUEST_URI']
        hit = get_publisher().process(input, env)
        response = hit.get_response()
        try:
            response.write(output)
            input.close()
            output.close()
            conn.close()
        except IOError, err:
            print "IOError while sending response: %s" % err


def run_web(site):
    logfile = open(site.get_logfile(), 'a')
    sys.stderr = sys.stdout = logfile
    site.start_durus_logging()
    site.get_publisher() # Make sure that the Publisher constructor works.
    clear_publisher()
    scgi_address = site.get_scgi_address()
    http_address = site.get_http_address()
    as_https_address = site.get_as_https_address()
    https_address = site.get_https_address()
    scgi_port = scgi_address and scgi_address[1]
    http_port = http_address and http_address[1]
    as_https_port = as_https_address and as_https_address[1]
    script_name = site.get_script_name()
    def create_connection_handler():
        class ConnectionHandler(object):
            def handle_connection(self, connection):
                port = connection.getsockname()[1]
                if port == scgi_port:
                    return SCGIHandler(
                        script_name=script_name).handle_connection(connection)
                elif port == http_port:
                    self.server_name, self.server_port = http_address
                    client_address = connection.getpeername()
                    HTTPRequestHandler(connection, client_address, self,
                                       SCRIPT_NAME=script_name)
                elif port == as_https_port:
                    self.server_name, self.server_port = https_address
                    client_address = connection.getpeername()
                    HTTPRequestHandler(
                        connection, client_address, self, HTTPS='on',
                        SCRIPT_NAME=script_name)
                else:
                    connection.send(str(locals()))
        site.get_create_publisher()()
        return ConnectionHandler()
    server = HubServer(create_connection_handler,
                       max_children=site.get_max_children(),
                       banned=site.get_banned())
    server.listen(scgi_address, http_address, as_https_address)
    site.ensure_uid_gid_not_root()
    def reexec(*args):
        server.do_stop()
        source = __file__
        if source.endswith('.pyc') or source.endswith('.pyo'):
            source = source[:-1]
        argv = [sys.executable, source, site.get_name()]
        print "RESTART:", " ".join(argv)
        os.execv(sys.executable, argv)
    signal(SIGHUP, reexec)
    server.run()

if __name__ == '__main__':
    run_web(Site(sys.argv[1]))


