From: Fredrik Tolf Date: Thu, 21 Oct 2010 10:03:49 +0000 (+0200) Subject: python: Added an alternative WSGI server that speaks ashd directly. X-Git-Tag: 0.4~17 X-Git-Url: http://www.dolda2000.com/gitweb/?p=ashd.git;a=commitdiff_plain;h=c270f222b55e805cd42c223da1c1d8774d6bd4cf python: Added an alternative WSGI server that speaks ashd directly. That is, avoiding the need to SCGI-wrap everything. It only works with CPython, though. --- diff --git a/python/ashd-wsgi b/python/ashd-wsgi new file mode 100755 index 0000000..d334765 --- /dev/null +++ b/python/ashd-wsgi @@ -0,0 +1,128 @@ +#!/usr/bin/python + +import sys, os, getopt, threading +import ashd.proto + +def usage(out): + out.write("usage: ashd-wsgi [-hA] [-p MODPATH] HANDLER-MODULE [ARGS...]\n") + +modwsgi_compat = False +opts, args = getopt.getopt(sys.argv[1:], "+hAp:") +for o, a in opts: + if o == "-h": + usage(sys.stdout) + sys.exit(0) + elif o == "-p": + sys.path.insert(0, a) + elif o == "-A": + modwsgi_compat = True +if len(args) < 1: + usage(sys.stderr) + sys.exit(1) + +try: + handlermod = __import__(args[0], fromlist = ["dummy"]) +except ImportError, exc: + sys.stderr.write("ashd-wsgi: handler %s not found: %s\n" % (args[0], exc.message)) + sys.exit(1) +if not modwsgi_compat: + if not hasattr(handlermod, "wmain"): + sys.stderr.write("ashd-wsgi: handler %s has no `wmain' function\n" % args[0]) + sys.exit(1) + handler = handlermod.wmain(args[1:]) +else: + if not hasattr(handlermod, "application"): + sys.stderr.write("ashd-wsgi: handler %s has no `application' object\n" % args[0]) + sys.exit(1) + handler = handlermod.application + +def dowsgi(req): + env = {} + env["wsgi.version"] = 1, 0 + for key, val in req.headers: + env["HTTP_" + key.upper().replace("-", "_")] = val + env["SERVER_SOFTWARE"] = "ashd-wsgi/1" + env["GATEWAY_INTERFACE"] = "CGI/1.1" + env["SERVER_PROTOCOL"] = req.ver + env["REQUEST_METHOD"] = req.method + env["REQUEST_URI"] = req.url + env["PATH_INFO"] = req.rest + name = req.url + p = name.find('?') + if p >= 0: + name = name[:p] + env["QUERY_STRING"] = name[p + 1:] + else: + env["QUERY_STRING"] = "" + if name[-len(req.rest):] == req.rest: + name = name[:-len(req.rest)] + env["SCRIPT_NAME"] = name + if "Host" in req: env["SERVER_NAME"] = req["Host"] + if "X-Ash-Server-Port" in req: env["SERVER_PORT"] = req["X-Ash-Server-Port"] + if "X-Ash-Protocol" in req and req["X-Ash-Protocol"] == "https": env["HTTPS"] = "on" + if "X-Ash-Address" in req: env["REMOTE_ADDR"] = req["X-Ash-Address"] + if "Content-Type" in req: env["CONTENT_TYPE"] = req["Content-Type"] + if "Content-Length" in req: env["CONTENT_LENGTH"] = req["Content-Length"] + if "X-Ash-File" in req: env["SCRIPT_FILENAME"] = req["X-Ash-File"] + if "X-Ash-Protocol" in req: env["wsgi.url_scheme"] = req["X-Ash-Protocol"] + env["wsgi.input"] = req.sk + env["wsgi.errors"] = sys.stderr + env["wsgi.multithread"] = True + env["wsgi.multiprocess"] = False + env["wsgi.run_once"] = False + + resp = [] + respsent = [] + + def write(data): + if not data: + return + if not respsent: + if not resp: + raise Exception, "Trying to write data before starting response." + status, headers = resp + respsent[:] = [True] + req.sk.write("HTTP/1.1 %s\n" % status) + for nm, val in headers: + req.sk.write("%s: %s\n" % (nm, val)) + req.sk.write("\n") + req.sk.write(data) + req.sk.flush() + + def startreq(status, headers, exc_info = None): + if resp: + if exc_info: # Interesting, this... + try: + if respsent: + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None # CPython GC bug? + else: + raise Exception, "Can only start responding once." + resp[:] = status, headers + return write + + respiter = handler(env, startreq) + try: + for data in respiter: + write(data) + write("") + finally: + if hasattr(respiter, "close"): + respiter.close() + +class reqthread(threading.Thread): + def __init__(self, req): + super(reqthread, self).__init__(name = "Request handler") + self.req = req.dup() + + def run(self): + try: + dowsgi(self.req) + finally: + self.req.close() + +def handle(req): + reqthread(req).start() + +ashd.proto.serveloop(handle) diff --git a/python/ashd/proto.py b/python/ashd/proto.py new file mode 100644 index 0000000..2c040c1 --- /dev/null +++ b/python/ashd/proto.py @@ -0,0 +1,95 @@ +import os, socket +import htlib + +class protoerr(Exception): + pass + +class req(object): + def __init__(self, method, url, ver, rest, headers, fd): + self.method = method + self.url = url + self.ver = ver + self.rest = rest + self.headers = headers + self.sk = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM).makefile('r+') + os.close(fd) + + def close(self): + self.sk.close() + + def __getitem__(self, header): + header = header.lower() + for key, val in self.headers: + if key.lower() == header: + return val + raise KeyError(header) + + def __contains__(self, header): + header = header.lower() + for key, val in self.headers: + if key.lower() == header: + return True + return False + + def dup(self): + return req(self.method, self.url, self.ver, self.rest, self.headers, os.dup(self.sk.fileno())) + + def match(self, match): + if self.rest[:len(match)] == match: + self.rest = self.rest[len(match):] + return True + return False + + def __str__(self): + return "\"%s %s %s\"" % (self.method, self.url, self.ver) + + def __enter__(self): + return self + + def __exit__(self, *excinfo): + self.sk.close() + return False + +def recvreq(sock = 0): + data, fd = htlib.recvfd(sock) + if fd is None: + return None + try: + print repr(data) + parts = data.split('\0')[:-1] + if len(parts) < 5: + raise protoerr("Truncated request") + method, url, ver, rest = parts[:4] + headers = [] + i = 4 + while True: + if parts[i] == "": break + if len(parts) - i < 3: + raise protoerr("Truncated request") + headers.append((parts[i], parts[i + 1])) + i += 2 + return req(method, url, ver, rest, headers, os.dup(fd)) + finally: + os.close(fd) + +def sendreq(sock, req): + data = "" + data += req.method + '\0' + data += req.url + '\0' + data += req.ver + '\0' + data += req.rest + '\0' + for key, val in req.headers: + data += key + '\0' + data += val + '\0' + data += '\0' + htlib.sendfd(sock, req.sk.fileno(), data) + +def serveloop(handler, sock = 0): + while True: + req = recvreq(sock) + if req is None: + break + try: + handler(req) + finally: + req.close()