##// END OF EJS Templates
hgweb: port archive command to modern response API...
Gregory Szorc -
r36892:97f44b07 default
parent child Browse files
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), self.headers.items())
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.append(('Content-Encoding', encoding))
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 req.req.qsparams:
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