##// 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 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,9 +408,10 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 return []
413 else:
412 wsgireq.respond(HTTP_OK, ctype)
414 wsgireq.respond(HTTP_OK, ctype)
413
414 return content
415 return content
415
416
416 except (error.LookupError, error.RepoLookupError) as err:
417 except (error.LookupError, error.RepoLookupError) as err:
@@ -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), self.headers.items())
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.append(('Content-Encoding', encoding))
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 req.req.qsparams:
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