Mercurial > public > mercurial-scm > hg
comparison mercurial/hgweb/webcommands.py @ 36876:97f44b0720e2
hgweb: port archive command to modern response API
Well, I tried to go with PEP 3333's recommendations and only allow
our WSGI application to emit data via a response generator.
Unfortunately, the "archive" command calls into the zipfile and
tarfile modules and these operator on file objects and must send
their data to an object with write(). There's no easy way turn
these write() calls into a generator.
So, we teach our response type how to expose a file object like
object that can be used to write() output. We try to keep the API
consistent with how things work currently: callers must call a
setbody*(), then sendresponse() to trigger sending of headers,
and only then can they get a handle on the object to perform
writing.
This required overloading the return value of @webcommand functions
even more. Fortunately, we're almost completely ported off the
legacy API. So we should be able to simplify matters in the near
future.
A test relying on this functionality has also been updated to use
the new API.
Differential Revision: https://phab.mercurial-scm.org/D2792
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 10 Mar 2018 20:16:20 -0800 |
parents | 16499427f6de |
children | 67fb0dca29bc |
comparison
equal
deleted
inserted
replaced
36875:16499427f6de | 36876:97f44b0720e2 |
---|---|
17 | 17 |
18 from .common import ( | 18 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 ) | |
27 from . import ( | |
28 request as requestmod, | |
29 ) | 25 ) |
30 | 26 |
31 from .. import ( | 27 from .. import ( |
32 archival, | 28 archival, |
33 dagop, | 29 dagop, |
62 about the HTTP response. | 58 about the HTTP response. |
63 | 59 |
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 |
71 @webcommand('mycommand') | 69 @webcommand('mycommand') |
72 def mycommand(web, req, tmpl): | 70 def mycommand(web, req, tmpl): |
1208 if not files: | 1206 if not files: |
1209 raise ErrorResponse(HTTP_NOT_FOUND, | 1207 raise ErrorResponse(HTTP_NOT_FOUND, |
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) | 1218 |
1219 req.respond(HTTP_OK, mimetype) | 1219 web.res.setbodywillwrite() |
1220 | 1220 assert list(web.res.sendresponse()) == [] |
1221 bodyfh = requestmod.offsettrackingwriter(req.write) | 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): |
1231 fname = req.req.qsparams['file'] | 1232 fname = req.req.qsparams['file'] |
1232 # a repo owner may set web.static in .hg/hgrc to get any file | 1233 # a repo owner may set web.static in .hg/hgrc to get any file |