/
wsgi.py
193 lines (144 loc) · 5.22 KB
/
wsgi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
"""WSGI Components
This module implements WSGI Components.
"""
from io import StringIO
from operator import itemgetter
from sys import exc_info as _exc_info
from traceback import format_tb
from circuits.core import BaseComponent, handler
from circuits.web import wrappers
from .dispatchers import Dispatcher
from .errors import httperror
from .events import request
from .http import HTTP
import httoop
from httoop.gateway.wsgi import WSGI
class WSGIGateway(WSGI):
def __init__(self, req, res, errors, path):
self.req = req
self.__errors = errors
self.__path = path
self.request = req.to_httoop()
self.response = res.to_httoop()
super().__init__()
def get_environ(self):
environ = super().get_environ()
env = environ.__setitem__
req = self.req
env("SERVER_NAME", req.host.split(":", 1)[0])
env("SERVER_PORT", "%i" % (req.server.port or 0))
env("SERVER_PROTOCOL", "HTTP/%d.%d" % req.server.http.protocol)
env("SCRIPT_NAME", req.script_name)
env("REMOTE_ADDR", req.remote.ip)
env("REMOTE_PORT", "%i" % (req.remote.port or 0))
env("wsgi.errors", self.__errors)
env("wsgi.run_once", False)
if req.path:
req.script_name = req.path[:len(self.__path)]
req.path = req.path[len(self.__path):]
env("SCRIPT_NAME", req.script_name)
env("PATH_INFO", req.path)
return environ
class WSGIClient(WSGI):
def __init__(self, *args, **kwargs):
self.request = httoop.Request()
self.response = httoop.Response()
super().__init__(*args, **kwargs)
class Application(BaseComponent):
channel = "web"
def init(self):
self._finished = False
HTTP(self).register(self)
Dispatcher().register(self)
def __call__(self, environ, start_response, exc_info=None):
wsgi = WSGIClient(environ, use_path_info=False)
for key, value in {
"HTTP_CGI_AUTHORIZATION": "Authorization",
"REMOTE_HOST": "Remote-Host",
"REMOTE_ADDR": "Remote-Addr",
}.items():
if key in environ:
wsgi.request.headers.append(value, environ[key])
self.request = wrappers.Request.from_httoop(wsgi.request, None)
self.request.path = wsgi.path_info
self.response = wrappers.Response.from_httoop(wsgi.response, self.request)
self.response.gzip = "gzip" in self.request.headers.get("Accept-Encoding", "")
self.request.remote = wrappers.Host(wsgi.remote_address, wsgi.remote_port)
self.request.script_name = wsgi.script_name
self.request.wsgi_environ = environ
self.fire(request(self.request, self.response))
self._finished = False
while self._queue or not self._finished:
self.tick()
self.response.prepare()
body = self.response.body
status = self.response.status
headers = list(self.response.headers.items())
start_response(str(status), headers, exc_info)
return body
@handler("response", channel="web")
def on_response(self, event, response):
self._finished = True
event.stop()
@property
def host(self):
return ""
@property
def port(self):
return 0
@property
def secure(self):
return False
class _Empty(str):
def __bool__(self):
return True
__nonzero__ = __bool__
empty = _Empty()
del _Empty
class Gateway(BaseComponent):
channel = "web"
def init(self, apps):
self.apps = apps
self.errors = {k: StringIO() for k in self.apps.keys()}
@handler("request", priority=0.2)
def _on_request(self, event, req, res):
if not self.apps:
return
parts = req.path.split("/")
candidates = []
for i in range(len(parts)):
k = "/".join(parts[:(i + 1)]) or "/"
if k in self.apps:
candidates.append((k, self.apps[k]))
candidates = sorted(candidates, key=itemgetter(0), reverse=True)
if not candidates:
return
path, app = candidates[0]
errors = self.errors[path]
wsgi = WSGIGateway(req, res, errors, path)
try:
wsgi(app)
res.__dict__.update(res.from_httoop(wsgi.response, req).__dict__)
if wsgi.response.body.chunked:
res.chunked = False
res.stream = True
res.body = iter(res.body)
return res
elif wsgi.response.body.fileable and wsgi.response.body.fd.tell():
wsgi.response.body.fd.seek(0)
wsgi.response.body.chunked = True
res.chunked = False
res.stream = True
def body_func():
yield from wsgi.response.body
res.body = body_func()
return res
elif not wsgi.response.body:
return empty
return res
except Exception as error:
etype, evalue, etraceback = _exc_info()
error = (etype, evalue, format_tb(etraceback))
return httperror(req, res, 500, error=error)
finally:
event.stop()