Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/hgweb/request.py @ 36867:a88d68dc3ee8
hgweb: create dedicated type for WSGI responses
We have refactored the request side of WSGI processing into a dedicated
type. Now let's do the same thing for the response side.
We invent a ``wsgiresponse`` type. It takes an instance of a
request (for consulation) and the WSGI application's "start_response"
handler.
The type basically allows setting the HTTP status line, response
headers, and the response body.
The WSGI application calls sendresponse() to start sending output.
Output is emitted as a generator to be fed through the WSGI application.
According to PEP 3333, this is the preferred way for output to be
transmitted. (Our legacy ``wsgirequest`` exposed a write() to send
data. We do not wish to support this API because it isn't recommended
by PEP 3333.)
The wire protocol code has been ported to use the new API.
Differential Revision: https://phab.mercurial-scm.org/D2775
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 10 Mar 2018 11:23:05 -0800 |
parents | 422be99519e5 |
children | ec0af9c59270 |
comparison
equal
deleted
inserted
replaced
36866:2859c6fa4fc2 | 36867:a88d68dc3ee8 |
---|---|
21 | 21 |
22 from ..thirdparty import ( | 22 from ..thirdparty import ( |
23 attr, | 23 attr, |
24 ) | 24 ) |
25 from .. import ( | 25 from .. import ( |
26 error, | |
26 pycompat, | 27 pycompat, |
27 util, | 28 util, |
28 ) | 29 ) |
29 | 30 |
30 @attr.s(frozen=True) | 31 @attr.s(frozen=True) |
199 querystringlist=querystringlist, | 200 querystringlist=querystringlist, |
200 querystringdict=querystringdict, | 201 querystringdict=querystringdict, |
201 headers=headers, | 202 headers=headers, |
202 bodyfh=bodyfh) | 203 bodyfh=bodyfh) |
203 | 204 |
205 class wsgiresponse(object): | |
206 """Represents a response to a WSGI request. | |
207 | |
208 A response consists of a status line, headers, and a body. | |
209 | |
210 Consumers must populate the ``status`` and ``headers`` fields and | |
211 make a call to a ``setbody*()`` method before the response can be | |
212 issued. | |
213 | |
214 When it is time to start sending the response over the wire, | |
215 ``sendresponse()`` is called. It handles emitting the header portion | |
216 of the response message. It then yields chunks of body data to be | |
217 written to the peer. Typically, the WSGI application itself calls | |
218 and returns the value from ``sendresponse()``. | |
219 """ | |
220 | |
221 def __init__(self, req, startresponse): | |
222 """Create an empty response tied to a specific request. | |
223 | |
224 ``req`` is a ``parsedrequest``. ``startresponse`` is the | |
225 ``start_response`` function passed to the WSGI application. | |
226 """ | |
227 self._req = req | |
228 self._startresponse = startresponse | |
229 | |
230 self.status = None | |
231 self.headers = wsgiheaders.Headers([]) | |
232 | |
233 self._bodybytes = None | |
234 self._bodygen = None | |
235 self._started = False | |
236 | |
237 def setbodybytes(self, b): | |
238 """Define the response body as static bytes.""" | |
239 if self._bodybytes is not None or self._bodygen is not None: | |
240 raise error.ProgrammingError('cannot define body multiple times') | |
241 | |
242 self._bodybytes = b | |
243 self.headers['Content-Length'] = '%d' % len(b) | |
244 | |
245 def setbodygen(self, gen): | |
246 """Define the response body as a generator of bytes.""" | |
247 if self._bodybytes is not None or self._bodygen is not None: | |
248 raise error.ProgrammingError('cannot define body multiple times') | |
249 | |
250 self._bodygen = gen | |
251 | |
252 def sendresponse(self): | |
253 """Send the generated response to the client. | |
254 | |
255 Before this is called, ``status`` must be set and one of | |
256 ``setbodybytes()`` or ``setbodygen()`` must be called. | |
257 | |
258 Calling this method multiple times is not allowed. | |
259 """ | |
260 if self._started: | |
261 raise error.ProgrammingError('sendresponse() called multiple times') | |
262 | |
263 self._started = True | |
264 | |
265 if not self.status: | |
266 raise error.ProgrammingError('status line not defined') | |
267 | |
268 if self._bodybytes is None and self._bodygen is None: | |
269 raise error.ProgrammingError('response body not defined') | |
270 | |
271 # Various HTTP clients (notably httplib) won't read the HTTP response | |
272 # until the HTTP request has been sent in full. If servers (us) send a | |
273 # response before the HTTP request has been fully sent, the connection | |
274 # may deadlock because neither end is reading. | |
275 # | |
276 # We work around this by "draining" the request data before | |
277 # sending any response in some conditions. | |
278 drain = False | |
279 close = False | |
280 | |
281 # If the client sent Expect: 100-continue, we assume it is smart enough | |
282 # to deal with the server sending a response before reading the request. | |
283 # (httplib doesn't do this.) | |
284 if self._req.headers.get('Expect', '').lower() == '100-continue': | |
285 pass | |
286 # Only tend to request methods that have bodies. Strictly speaking, | |
287 # we should sniff for a body. But this is fine for our existing | |
288 # WSGI applications. | |
289 elif self._req.method not in ('POST', 'PUT'): | |
290 pass | |
291 else: | |
292 # If we don't know how much data to read, there's no guarantee | |
293 # that we can drain the request responsibly. The WSGI | |
294 # specification only says that servers *should* ensure the | |
295 # input stream doesn't overrun the actual request. So there's | |
296 # no guarantee that reading until EOF won't corrupt the stream | |
297 # state. | |
298 if not isinstance(self._req.bodyfh, util.cappedreader): | |
299 close = True | |
300 else: | |
301 # We /could/ only drain certain HTTP response codes. But 200 and | |
302 # non-200 wire protocol responses both require draining. Since | |
303 # we have a capped reader in place for all situations where we | |
304 # drain, it is safe to read from that stream. We'll either do | |
305 # a drain or no-op if we're already at EOF. | |
306 drain = True | |
307 | |
308 if close: | |
309 self.headers['Connection'] = 'Close' | |
310 | |
311 if drain: | |
312 assert isinstance(self._req.bodyfh, util.cappedreader) | |
313 while True: | |
314 chunk = self._req.bodyfh.read(32768) | |
315 if not chunk: | |
316 break | |
317 | |
318 self._startresponse(pycompat.sysstr(self.status), self.headers.items()) | |
319 if self._bodybytes: | |
320 yield self._bodybytes | |
321 elif self._bodygen: | |
322 for chunk in self._bodygen: | |
323 yield chunk | |
324 else: | |
325 error.ProgrammingError('do not know how to send body') | |
326 | |
204 class wsgirequest(object): | 327 class wsgirequest(object): |
205 """Higher-level API for a WSGI request. | 328 """Higher-level API for a WSGI request. |
206 | 329 |
207 WSGI applications are invoked with 2 arguments. They are used to | 330 WSGI applications are invoked with 2 arguments. They are used to |
208 instantiate instances of this class, which provides higher-level APIs | 331 instantiate instances of this class, which provides higher-level APIs |
226 self.multiprocess = wsgienv[r'wsgi.multiprocess'] | 349 self.multiprocess = wsgienv[r'wsgi.multiprocess'] |
227 self.run_once = wsgienv[r'wsgi.run_once'] | 350 self.run_once = wsgienv[r'wsgi.run_once'] |
228 self.env = wsgienv | 351 self.env = wsgienv |
229 self.req = parserequestfromenv(wsgienv, inp) | 352 self.req = parserequestfromenv(wsgienv, inp) |
230 self.form = self.req.querystringdict | 353 self.form = self.req.querystringdict |
354 self.res = wsgiresponse(self.req, start_response) | |
231 self._start_response = start_response | 355 self._start_response = start_response |
232 self.server_write = None | 356 self.server_write = None |
233 self.headers = [] | 357 self.headers = [] |
234 | 358 |
235 def respond(self, status, type, filename=None, body=None): | 359 def respond(self, status, type, filename=None, body=None): |