python: Initial porting of the Python code to Python 3.
[ashd.git] / python / ashd / scgi.py
CommitLineData
55fa3f63 1import sys, collections
c06db49a
FT
2import threading
3
4class protoerr(Exception):
5 pass
6
81a0ca30
FT
7class closed(IOError):
8 def __init__(self):
9 super(closed, self).__init__("The client has closed the connection.")
10
c06db49a
FT
11def readns(sk):
12 hln = 0
13 while True:
14 c = sk.read(1)
55fa3f63 15 if c == b':':
c06db49a 16 break
55fa3f63
FT
17 elif c >= b'0' or c <= b'9':
18 hln = (hln * 10) + (ord(c) - ord(b'0'))
c06db49a 19 else:
55fa3f63 20 raise protoerr("Invalid netstring length byte: " + c)
c06db49a 21 ret = sk.read(hln)
55fa3f63
FT
22 if sk.read(1) != b',':
23 raise protoerr("Non-terminated netstring")
c06db49a
FT
24 return ret
25
26def readhead(sk):
55fa3f63 27 parts = readns(sk).split(b'\0')[:-1]
c06db49a 28 if len(parts) % 2 != 0:
55fa3f63 29 raise protoerr("Malformed headers")
c06db49a
FT
30 ret = {}
31 i = 0
32 while i < len(parts):
33 ret[parts[i]] = parts[i + 1]
34 i += 2
35 return ret
36
37class reqthread(threading.Thread):
38 def __init__(self, sk, handler):
39 super(reqthread, self).__init__(name = "SCGI request handler")
55fa3f63 40 self.sk = sk.dup().makefile("rwb")
c06db49a
FT
41 self.handler = handler
42
43 def run(self):
44 try:
45 head = readhead(self.sk)
46 self.handler(head, self.sk)
47 finally:
48 self.sk.close()
49
50def handlescgi(sk, handler):
51 t = reqthread(sk, handler)
52 t.start()
53
54def servescgi(socket, handler):
55 while True:
56 nsk, addr = socket.accept()
57 try:
58 handlescgi(nsk, handler)
59 finally:
60 nsk.close()
61
55fa3f63
FT
62def decodehead(head, coding):
63 return {k.decode(coding): v.decode(coding) for k, v in head.items()}
64
c06db49a
FT
65def wrapwsgi(handler):
66 def handle(head, sk):
55fa3f63
FT
67 try:
68 env = decodehead(head, "utf-8")
69 env["wsgi.uri_encoding"] = "utf-8"
70 except UnicodeError:
71 env = decodehead(head, "latin-1")
72 env["wsgi.uri_encoding"] = "latin-1"
c06db49a
FT
73 env["wsgi.version"] = 1, 0
74 if "HTTP_X_ASH_PROTOCOL" in env:
75 env["wsgi.url_scheme"] = env["HTTP_X_ASH_PROTOCOL"]
76 elif "HTTPS" in env:
77 env["wsgi.url_scheme"] = "https"
78 else:
79 env["wsgi.url_scheme"] = "http"
80 env["wsgi.input"] = sk
81 env["wsgi.errors"] = sys.stderr
82 env["wsgi.multithread"] = True
83 env["wsgi.multiprocess"] = False
84 env["wsgi.run_once"] = False
85
86 resp = []
87 respsent = []
88
55fa3f63
FT
89 def recode(thing):
90 if isinstance(thing, collections.ByteString):
91 return thing
92 else:
93 return str(thing).encode("latin-1")
94
699754de 95 def flushreq():
c06db49a
FT
96 if not respsent:
97 if not resp:
55fa3f63 98 raise Exception("Trying to write data before starting response.")
c06db49a
FT
99 status, headers = resp
100 respsent[:] = [True]
55fa3f63
FT
101 buf = bytearray()
102 buf += b"Status: " + recode(status) + b"\n"
103 for nm, val in headers:
104 buf += recode(nm) + b": " + recode(val) + b"\n"
105 buf += b"\n"
8bb0e3c1 106 try:
55fa3f63 107 sk.write(buf)
8bb0e3c1
FT
108 except IOError:
109 raise closed()
699754de
FT
110
111 def write(data):
112 if not data:
113 return
8bb0e3c1 114 flushreq()
81a0ca30 115 try:
81a0ca30
FT
116 sk.write(data)
117 sk.flush()
118 except IOError:
119 raise closed()
c06db49a
FT
120
121 def startreq(status, headers, exc_info = None):
122 if resp:
123 if exc_info: # Interesting, this...
124 try:
125 if respsent:
55fa3f63 126 raise exc_info[1]
c06db49a
FT
127 finally:
128 exc_info = None # CPython GC bug?
129 else:
55fa3f63 130 raise Exception("Can only start responding once.")
c06db49a
FT
131 resp[:] = status, headers
132 return write
133
134 respiter = handler(env, startreq)
135 try:
8bb0e3c1
FT
136 try:
137 for data in respiter:
138 write(data)
139 if resp:
140 flushreq()
141 except closed:
142 pass
c06db49a
FT
143 finally:
144 if hasattr(respiter, "close"):
145 respiter.close()
146 return handle