doc: Fixed typo.
[ashd.git] / python3 / ashd / scgi.py
CommitLineData
173e0e9e
FT
1import sys, collections
2import threading
3
4class protoerr(Exception):
5 pass
6
7class closed(IOError):
8 def __init__(self):
9 super(closed, self).__init__("The client has closed the connection.")
10
11def readns(sk):
12 hln = 0
13 while True:
14 c = sk.read(1)
15 if c == b':':
16 break
17 elif c >= b'0' or c <= b'9':
18 hln = (hln * 10) + (ord(c) - ord(b'0'))
19 else:
20 raise protoerr("Invalid netstring length byte: " + c)
21 ret = sk.read(hln)
22 if sk.read(1) != b',':
23 raise protoerr("Non-terminated netstring")
24 return ret
25
26def readhead(sk):
27 parts = readns(sk).split(b'\0')[:-1]
28 if len(parts) % 2 != 0:
29 raise protoerr("Malformed headers")
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")
40 self.sk = sk.dup().makefile("rwb")
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
62def decodehead(head, coding):
63 return {k.decode(coding): v.decode(coding) for k, v in head.items()}
64
65def wrapwsgi(handler):
66 def handle(head, sk):
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"
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
89 def recode(thing):
90 if isinstance(thing, collections.ByteString):
91 return thing
92 else:
93 return str(thing).encode("latin-1")
94
95 def flushreq():
96 if not respsent:
97 if not resp:
98 raise Exception("Trying to write data before starting response.")
99 status, headers = resp
100 respsent[:] = [True]
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"
106 try:
107 sk.write(buf)
108 except IOError:
109 raise closed()
110
111 def write(data):
112 if not data:
113 return
114 flushreq()
115 try:
116 sk.write(data)
117 sk.flush()
118 except IOError:
119 raise closed()
120
121 def startreq(status, headers, exc_info = None):
122 if resp:
123 if exc_info: # Interesting, this...
124 try:
125 if respsent:
126 raise exc_info[1]
127 finally:
128 exc_info = None # CPython GC bug?
129 else:
130 raise Exception("Can only start responding once.")
131 resp[:] = status, headers
132 return write
133
134 respiter = handler(env, startreq)
135 try:
136 try:
137 for data in respiter:
138 write(data)
139 if resp:
140 flushreq()
141 except closed:
142 pass
143 finally:
144 if hasattr(respiter, "close"):
145 respiter.close()
146 return handle