Show More
@@ -624,6 +624,9 b' def kwweb_skip(orig, web, req, tmpl):' | |||
|
624 | 624 | res = orig(web, req, tmpl) |
|
625 | 625 | if res is web.res: |
|
626 | 626 | res = res.sendresponse() |
|
627 | elif res is True: | |
|
628 | return | |
|
629 | ||
|
627 | 630 | for chunk in res: |
|
628 | 631 | yield chunk |
|
629 | 632 | finally: |
@@ -408,9 +408,10 b' class hgweb(object):' | |||
|
408 | 408 | |
|
409 | 409 | if content is res: |
|
410 | 410 | return res.sendresponse() |
|
411 | ||
|
411 | elif content is True: | |
|
412 | return [] | |
|
413 | else: | |
|
412 | 414 | wsgireq.respond(HTTP_OK, ctype) |
|
413 | ||
|
414 | 415 | return content |
|
415 | 416 | |
|
416 | 417 | except (error.LookupError, error.RepoLookupError) as err: |
@@ -351,22 +351,42 b' class wsgiresponse(object):' | |||
|
351 | 351 | |
|
352 | 352 | self._bodybytes = None |
|
353 | 353 | self._bodygen = None |
|
354 | self._bodywillwrite = False | |
|
354 | 355 | self._started = False |
|
356 | self._bodywritefn = None | |
|
357 | ||
|
358 | def _verifybody(self): | |
|
359 | if (self._bodybytes is not None or self._bodygen is not None | |
|
360 | or self._bodywillwrite): | |
|
361 | raise error.ProgrammingError('cannot define body multiple times') | |
|
355 | 362 | |
|
356 | 363 | def setbodybytes(self, b): |
|
357 | 364 | """Define the response body as static bytes.""" |
|
358 | if self._bodybytes is not None or self._bodygen is not None: | |
|
359 | raise error.ProgrammingError('cannot define body multiple times') | |
|
360 | ||
|
365 | self._verifybody() | |
|
361 | 366 | self._bodybytes = b |
|
362 | 367 | self.headers['Content-Length'] = '%d' % len(b) |
|
363 | 368 | |
|
364 | 369 | def setbodygen(self, gen): |
|
365 | 370 | """Define the response body as a generator of bytes.""" |
|
366 | if self._bodybytes is not None or self._bodygen is not None: | |
|
367 | raise error.ProgrammingError('cannot define body multiple times') | |
|
371 | self._verifybody() | |
|
372 | self._bodygen = gen | |
|
373 | ||
|
374 | def setbodywillwrite(self): | |
|
375 | """Signal an intent to use write() to emit the response body. | |
|
376 | ||
|
377 | **This is the least preferred way to send a body.** | |
|
368 | 378 |
|
|
369 | self._bodygen = gen | |
|
379 | It is preferred for WSGI applications to emit a generator of chunks | |
|
380 | constituting the response body. However, some consumers can't emit | |
|
381 | data this way. So, WSGI provides a way to obtain a ``write(data)`` | |
|
382 | function that can be used to synchronously perform an unbuffered | |
|
383 | write. | |
|
384 | ||
|
385 | Calling this function signals an intent to produce the body in this | |
|
386 | manner. | |
|
387 | """ | |
|
388 | self._verifybody() | |
|
389 | self._bodywillwrite = True | |
|
370 | 390 | |
|
371 | 391 | def sendresponse(self): |
|
372 | 392 | """Send the generated response to the client. |
@@ -384,7 +404,8 b' class wsgiresponse(object):' | |||
|
384 | 404 | if not self.status: |
|
385 | 405 | raise error.ProgrammingError('status line not defined') |
|
386 | 406 | |
|
387 |
if self._bodybytes is None and self._bodygen is None |
|
|
407 | if (self._bodybytes is None and self._bodygen is None | |
|
408 | and not self._bodywillwrite): | |
|
388 | 409 | raise error.ProgrammingError('response body not defined') |
|
389 | 410 | |
|
390 | 411 | # Various HTTP clients (notably httplib) won't read the HTTP response |
@@ -434,15 +455,40 b' class wsgiresponse(object):' | |||
|
434 | 455 | if not chunk: |
|
435 | 456 | break |
|
436 | 457 | |
|
437 |
self._startresponse(pycompat.sysstr(self.status), |
|
|
458 | write = self._startresponse(pycompat.sysstr(self.status), | |
|
459 | self.headers.items()) | |
|
460 | ||
|
438 | 461 | if self._bodybytes: |
|
439 | 462 | yield self._bodybytes |
|
440 | 463 | elif self._bodygen: |
|
441 | 464 | for chunk in self._bodygen: |
|
442 | 465 | yield chunk |
|
466 | elif self._bodywillwrite: | |
|
467 | self._bodywritefn = write | |
|
443 | 468 | else: |
|
444 | 469 | error.ProgrammingError('do not know how to send body') |
|
445 | 470 | |
|
471 | def getbodyfile(self): | |
|
472 | """Obtain a file object like object representing the response body. | |
|
473 | ||
|
474 | For this to work, you must call ``setbodywillwrite()`` and then | |
|
475 | ``sendresponse()`` first. ``sendresponse()`` is a generator and the | |
|
476 | function won't run to completion unless the generator is advanced. The | |
|
477 | generator yields not items. The easiest way to consume it is with | |
|
478 | ``list(res.sendresponse())``, which should resolve to an empty list - | |
|
479 | ``[]``. | |
|
480 | """ | |
|
481 | if not self._bodywillwrite: | |
|
482 | raise error.ProgrammingError('must call setbodywillwrite() first') | |
|
483 | ||
|
484 | if not self._started: | |
|
485 | raise error.ProgrammingError('must call sendresponse() first; did ' | |
|
486 | 'you remember to consume it since it ' | |
|
487 | 'is a generator?') | |
|
488 | ||
|
489 | assert self._bodywritefn | |
|
490 | return offsettrackingwriter(self._bodywritefn) | |
|
491 | ||
|
446 | 492 | class wsgirequest(object): |
|
447 | 493 | """Higher-level API for a WSGI request. |
|
448 | 494 |
@@ -19,14 +19,10 b' from .common import (' | |||
|
19 | 19 | ErrorResponse, |
|
20 | 20 | HTTP_FORBIDDEN, |
|
21 | 21 | HTTP_NOT_FOUND, |
|
22 | HTTP_OK, | |
|
23 | 22 | get_contact, |
|
24 | 23 | paritygen, |
|
25 | 24 | staticfile, |
|
26 | 25 | ) |
|
27 | from . import ( | |
|
28 | request as requestmod, | |
|
29 | ) | |
|
30 | 26 | |
|
31 | 27 | from .. import ( |
|
32 | 28 | archival, |
@@ -64,7 +60,9 b' class webcommand(object):' | |||
|
64 | 60 | The function can return the ``requestcontext.res`` instance to signal |
|
65 | 61 | that it wants to use this object to generate the response. If an iterable |
|
66 | 62 | is returned, the ``wsgirequest`` instance will be used and the returned |
|
67 | content will constitute the response body. | |
|
63 | content will constitute the response body. ``True`` can be returned to | |
|
64 | indicate that the function already sent output and the caller doesn't | |
|
65 | need to do anything more to send the response. | |
|
68 | 66 | |
|
69 | 67 | Usage: |
|
70 | 68 | |
@@ -1210,21 +1208,24 b' def archive(web, req, tmpl):' | |||
|
1210 | 1208 | 'file(s) not found: %s' % file) |
|
1211 | 1209 | |
|
1212 | 1210 | mimetype, artype, extension, encoding = web.archivespecs[type_] |
|
1213 | headers = [ | |
|
1214 | ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension)) | |
|
1215 | ] | |
|
1211 | ||
|
1212 | web.res.headers['Content-Type'] = mimetype | |
|
1213 | web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( | |
|
1214 | name, extension) | |
|
1215 | ||
|
1216 | 1216 | if encoding: |
|
1217 |
headers |
|
|
1218 | req.headers.extend(headers) | |
|
1219 | req.respond(HTTP_OK, mimetype) | |
|
1217 | web.res.headers['Content-Encoding'] = encoding | |
|
1220 | 1218 | |
|
1221 | bodyfh = requestmod.offsettrackingwriter(req.write) | |
|
1219 | web.res.setbodywillwrite() | |
|
1220 | assert list(web.res.sendresponse()) == [] | |
|
1221 | ||
|
1222 | bodyfh = web.res.getbodyfile() | |
|
1222 | 1223 | |
|
1223 | 1224 | archival.archive(web.repo, bodyfh, cnode, artype, prefix=name, |
|
1224 | 1225 | matchfn=match, |
|
1225 | 1226 | subrepos=web.configbool("web", "archivesubrepos")) |
|
1226 | return [] | |
|
1227 | 1227 | |
|
1228 | return True | |
|
1228 | 1229 | |
|
1229 | 1230 | @webcommand('static') |
|
1230 | 1231 | def static(web, req, tmpl): |
@@ -10,9 +10,12 b' def raiseerror(web, req, tmpl):' | |||
|
10 | 10 | '''Dummy web command that raises an uncaught Exception.''' |
|
11 | 11 | |
|
12 | 12 | # Simulate an error after partial response. |
|
13 |
if 'partialresponse' in |
|
|
14 | req.respond(200, 'text/plain') | |
|
15 | req.write('partial content\n') | |
|
13 | if 'partialresponse' in web.req.qsparams: | |
|
14 | web.res.status = b'200 Script output follows' | |
|
15 | web.res.headers[b'Content-Type'] = b'text/plain' | |
|
16 | web.res.setbodywillwrite() | |
|
17 | list(web.res.sendresponse()) | |
|
18 | web.res.getbodyfile().write(b'partial content\n') | |
|
16 | 19 | |
|
17 | 20 | raise AttributeError('I am an uncaught error!') |
|
18 | 21 |
General Comments 0
You need to be logged in to leave comments.
Login now