Added a SCGI-WSGI gateway for Python.
[ashd.git] / python / ashd / scgi.py
1 import sys
2 import threading
3
4 class protoerr(Exception):
5     pass
6
7 def readns(sk):
8     hln = 0
9     while True:
10         c = sk.read(1)
11         if c == ':':
12             break
13         elif c >= '0' or c <= '9':
14             hln = (hln * 10) + (ord(c) - ord('0'))
15         else:
16             raise protoerr, "Invalid netstring length byte: " + c
17     ret = sk.read(hln)
18     if sk.read(1) != ',':
19         raise protoerr, "Non-terminated netstring"
20     return ret
21
22 def readhead(sk):
23     parts = readns(sk).split('\0')[:-1]
24     if len(parts) % 2 != 0:
25         raise protoerr, "Malformed headers"
26     ret = {}
27     i = 0
28     while i < len(parts):
29         ret[parts[i]] = parts[i + 1]
30         i += 2
31     return ret
32
33 class reqthread(threading.Thread):
34     def __init__(self, sk, handler):
35         super(reqthread, self).__init__(name = "SCGI request handler")
36         self.sk = sk.dup().makefile("r+")
37         self.handler = handler
38
39     def run(self):
40         try:
41             head = readhead(self.sk)
42             self.handler(head, self.sk)
43         finally:
44             self.sk.close()
45
46 def handlescgi(sk, handler):
47     t = reqthread(sk, handler)
48     t.start()
49
50 def servescgi(socket, handler):
51     while True:
52         nsk, addr = socket.accept()
53         try:
54             handlescgi(nsk, handler)
55         finally:
56             nsk.close()
57
58 def wrapwsgi(handler):
59     def handle(head, sk):
60         env = dict(head)
61         env["wsgi.version"] = 1, 0
62         if "HTTP_X_ASH_PROTOCOL" in env:
63             env["wsgi.url_scheme"] = env["HTTP_X_ASH_PROTOCOL"]
64         elif "HTTPS" in env:
65             env["wsgi.url_scheme"] = "https"
66         else:
67             env["wsgi.url_scheme"] = "http"
68         env["wsgi.input"] = sk
69         env["wsgi.errors"] = sys.stderr
70         env["wsgi.multithread"] = True
71         env["wsgi.multiprocess"] = False
72         env["wsgi.run_once"] = False
73
74         resp = []
75         respsent = []
76
77         def write(data):
78             if not data:
79                 return
80             if not respsent:
81                 if not resp:
82                     raise Exception, "Trying to write data before starting response."
83                 status, headers = resp
84                 respsent[:] = [True]
85                 sk.write("Status: %s\n" % status)
86                 for nm, val in headers:
87                     sk.write("%s: %s\n" % (nm, val))
88                 sk.write("\n")
89             sk.write(data)
90             sk.flush()
91
92         def startreq(status, headers, exc_info = None):
93             if resp:
94                 if exc_info:                # Interesting, this...
95                     try:
96                         if respsent:
97                             raise exc_info[0], exc_info[1], exc_info[2]
98                     finally:
99                         exc_info = None     # CPython GC bug?
100                 else:
101                     raise Exception, "Can only start responding once."
102             resp[:] = status, headers
103             return write
104
105         respiter = handler(env, startreq)
106         try:
107             for data in respiter:
108                 write(data)
109             write("")
110         finally:
111             if hasattr(respiter, "close"):
112                 respiter.close()
113     return handle