Show More
@@ -624,6 +624,9 b' def kwweb_skip(orig, web, req, tmpl):' | |||||
624 | res = orig(web, req, tmpl) |
|
624 | res = orig(web, req, tmpl) | |
625 | if res is web.res: |
|
625 | if res is web.res: | |
626 | res = res.sendresponse() |
|
626 | res = res.sendresponse() | |
|
627 | elif res is True: | |||
|
628 | return | |||
|
629 | ||||
627 | for chunk in res: |
|
630 | for chunk in res: | |
628 | yield chunk |
|
631 | yield chunk | |
629 | finally: |
|
632 | finally: |
@@ -408,10 +408,11 b' class hgweb(object):' | |||||
408 |
|
408 | |||
409 | if content is res: |
|
409 | if content is res: | |
410 | return res.sendresponse() |
|
410 | return res.sendresponse() | |
411 |
|
411 | elif content is True: | ||
412 | wsgireq.respond(HTTP_OK, ctype) |
|
412 | return [] | |
413 |
|
413 | else: | ||
414 | return content |
|
414 | wsgireq.respond(HTTP_OK, ctype) | |
|
415 | return content | |||
415 |
|
416 | |||
416 | except (error.LookupError, error.RepoLookupError) as err: |
|
417 | except (error.LookupError, error.RepoLookupError) as err: | |
417 | wsgireq.respond(HTTP_NOT_FOUND, ctype) |
|
418 | wsgireq.respond(HTTP_NOT_FOUND, ctype) |
@@ -351,22 +351,42 b' class wsgiresponse(object):' | |||||
351 |
|
351 | |||
352 | self._bodybytes = None |
|
352 | self._bodybytes = None | |
353 | self._bodygen = None |
|
353 | self._bodygen = None | |
|
354 | self._bodywillwrite = False | |||
354 | self._started = False |
|
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 | def setbodybytes(self, b): |
|
363 | def setbodybytes(self, b): | |
357 | """Define the response body as static bytes.""" |
|
364 | """Define the response body as static bytes.""" | |
358 | if self._bodybytes is not None or self._bodygen is not None: |
|
365 | self._verifybody() | |
359 | raise error.ProgrammingError('cannot define body multiple times') |
|
|||
360 |
|
||||
361 | self._bodybytes = b |
|
366 | self._bodybytes = b | |
362 | self.headers['Content-Length'] = '%d' % len(b) |
|
367 | self.headers['Content-Length'] = '%d' % len(b) | |
363 |
|
368 | |||
364 | def setbodygen(self, gen): |
|
369 | def setbodygen(self, gen): | |
365 | """Define the response body as a generator of bytes.""" |
|
370 | """Define the response body as a generator of bytes.""" | |
366 | if self._bodybytes is not None or self._bodygen is not None: |
|
371 | self._verifybody() | |
367 | raise error.ProgrammingError('cannot define body multiple times') |
|
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 | def sendresponse(self): |
|
391 | def sendresponse(self): | |
372 | """Send the generated response to the client. |
|
392 | """Send the generated response to the client. | |
@@ -384,7 +404,8 b' class wsgiresponse(object):' | |||||
384 | if not self.status: |
|
404 | if not self.status: | |
385 | raise error.ProgrammingError('status line not defined') |
|
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 | raise error.ProgrammingError('response body not defined') |
|
409 | raise error.ProgrammingError('response body not defined') | |
389 |
|
410 | |||
390 | # Various HTTP clients (notably httplib) won't read the HTTP response |
|
411 | # Various HTTP clients (notably httplib) won't read the HTTP response | |
@@ -434,15 +455,40 b' class wsgiresponse(object):' | |||||
434 | if not chunk: |
|
455 | if not chunk: | |
435 | break |
|
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 | if self._bodybytes: |
|
461 | if self._bodybytes: | |
439 | yield self._bodybytes |
|
462 | yield self._bodybytes | |
440 | elif self._bodygen: |
|
463 | elif self._bodygen: | |
441 | for chunk in self._bodygen: |
|
464 | for chunk in self._bodygen: | |
442 | yield chunk |
|
465 | yield chunk | |
|
466 | elif self._bodywillwrite: | |||
|
467 | self._bodywritefn = write | |||
443 | else: |
|
468 | else: | |
444 | error.ProgrammingError('do not know how to send body') |
|
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 | class wsgirequest(object): |
|
492 | class wsgirequest(object): | |
447 | """Higher-level API for a WSGI request. |
|
493 | """Higher-level API for a WSGI request. | |
448 |
|
494 |
@@ -19,14 +19,10 b' from .common import (' | |||||
19 | ErrorResponse, |
|
19 | ErrorResponse, | |
20 | HTTP_FORBIDDEN, |
|
20 | HTTP_FORBIDDEN, | |
21 | HTTP_NOT_FOUND, |
|
21 | HTTP_NOT_FOUND, | |
22 | HTTP_OK, |
|
|||
23 | get_contact, |
|
22 | get_contact, | |
24 | paritygen, |
|
23 | paritygen, | |
25 | staticfile, |
|
24 | staticfile, | |
26 | ) |
|
25 | ) | |
27 | from . import ( |
|
|||
28 | request as requestmod, |
|
|||
29 | ) |
|
|||
30 |
|
26 | |||
31 | from .. import ( |
|
27 | from .. import ( | |
32 | archival, |
|
28 | archival, | |
@@ -64,7 +60,9 b' class webcommand(object):' | |||||
64 | The function can return the ``requestcontext.res`` instance to signal |
|
60 | The function can return the ``requestcontext.res`` instance to signal | |
65 | that it wants to use this object to generate the response. If an iterable |
|
61 | that it wants to use this object to generate the response. If an iterable | |
66 | is returned, the ``wsgirequest`` instance will be used and the returned |
|
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 | Usage: |
|
67 | Usage: | |
70 |
|
68 | |||
@@ -1210,21 +1208,24 b' def archive(web, req, tmpl):' | |||||
1210 | 'file(s) not found: %s' % file) |
|
1208 | 'file(s) not found: %s' % file) | |
1211 |
|
1209 | |||
1212 | mimetype, artype, extension, encoding = web.archivespecs[type_] |
|
1210 | mimetype, artype, extension, encoding = web.archivespecs[type_] | |
1213 | headers = [ |
|
1211 | ||
1214 | ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension)) |
|
1212 | web.res.headers['Content-Type'] = mimetype | |
1215 | ] |
|
1213 | web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( | |
|
1214 | name, extension) | |||
|
1215 | ||||
1216 | if encoding: |
|
1216 | if encoding: | |
1217 |
headers |
|
1217 | web.res.headers['Content-Encoding'] = encoding | |
1218 | req.headers.extend(headers) |
|
|||
1219 | req.respond(HTTP_OK, mimetype) |
|
|||
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 | archival.archive(web.repo, bodyfh, cnode, artype, prefix=name, |
|
1224 | archival.archive(web.repo, bodyfh, cnode, artype, prefix=name, | |
1224 | matchfn=match, |
|
1225 | matchfn=match, | |
1225 | subrepos=web.configbool("web", "archivesubrepos")) |
|
1226 | subrepos=web.configbool("web", "archivesubrepos")) | |
1226 | return [] |
|
|||
1227 |
|
1227 | |||
|
1228 | return True | |||
1228 |
|
1229 | |||
1229 | @webcommand('static') |
|
1230 | @webcommand('static') | |
1230 | def static(web, req, tmpl): |
|
1231 | def static(web, req, tmpl): |
@@ -10,9 +10,12 b' def raiseerror(web, req, tmpl):' | |||||
10 | '''Dummy web command that raises an uncaught Exception.''' |
|
10 | '''Dummy web command that raises an uncaught Exception.''' | |
11 |
|
11 | |||
12 | # Simulate an error after partial response. |
|
12 | # Simulate an error after partial response. | |
13 |
if 'partialresponse' in |
|
13 | if 'partialresponse' in web.req.qsparams: | |
14 | req.respond(200, 'text/plain') |
|
14 | web.res.status = b'200 Script output follows' | |
15 | req.write('partial content\n') |
|
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 | raise AttributeError('I am an uncaught error!') |
|
20 | raise AttributeError('I am an uncaught error!') | |
18 |
|
21 |
General Comments 0
You need to be logged in to leave comments.
Login now