Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/hgweb/request.py @ 36881:16499427f6de
hgweb: refactor fake file object proxy for archiving
Python's zip file writer operates on a file object. When doing work,
it periodically calls write(), flush(), and tell() on that object.
In WSGI contexts, the start_response function returns a write()
function. That's a function to write data, not a full file object.
So, when the archival code was first introduced in 2b03c6733efa in
2006, someone invented a proxy "tellable" type that wrapped a file
object like object and kept track of write count so it could
implement tell() and satisfy zipfile's needs.
When our archival code runs, it attempts to tell() the destination
and if that fails, converts it to a "tellable" instance. Our WSGI
application passes the "wsgirequest" instance to the archival
function. It fails the tell() test and is converted to a "tellable."
It's worth noting that "wsgirequest" implements flush(), so
"tellable" doesn't.
This hackery all seems very specific to the WSGI code. So this commit
moves the "tellable" type and the conversion of the destination file
object into the WSGI code. There's a chance some other caller may be
passing a file object like object that doesn't implement tell(). But
I doubt it.
As part of the refactor, our new type implements flush() and doesn't
implement __getattr__. Given the intended limited use of this type,
I want things to fail fast if there is an attempt to access attributes
because I think it is important to document which attributes are being
used for what purposes.
Differential Revision: https://phab.mercurial-scm.org/D2791
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 10 Mar 2018 16:17:51 -0800 |
parents | 8ddb5c354906 |
children | 97f44b0720e2 |
comparison
equal
deleted
inserted
replaced
36880:40193f977a8b | 36881:16499427f6de |
---|---|
288 querystring=querystring, | 288 querystring=querystring, |
289 qsparams=qsparams, | 289 qsparams=qsparams, |
290 headers=headers, | 290 headers=headers, |
291 bodyfh=bodyfh) | 291 bodyfh=bodyfh) |
292 | 292 |
293 class offsettrackingwriter(object): | |
294 """A file object like object that is append only and tracks write count. | |
295 | |
296 Instances are bound to a callable. This callable is called with data | |
297 whenever a ``write()`` is attempted. | |
298 | |
299 Instances track the amount of written data so they can answer ``tell()`` | |
300 requests. | |
301 | |
302 The intent of this class is to wrap the ``write()`` function returned by | |
303 a WSGI ``start_response()`` function. Since ``write()`` is a callable and | |
304 not a file object, it doesn't implement other file object methods. | |
305 """ | |
306 def __init__(self, writefn): | |
307 self._write = writefn | |
308 self._offset = 0 | |
309 | |
310 def write(self, s): | |
311 res = self._write(s) | |
312 # Some Python objects don't report the number of bytes written. | |
313 if res is None: | |
314 self._offset += len(s) | |
315 else: | |
316 self._offset += res | |
317 | |
318 def flush(self): | |
319 pass | |
320 | |
321 def tell(self): | |
322 return self._offset | |
323 | |
293 class wsgiresponse(object): | 324 class wsgiresponse(object): |
294 """Represents a response to a WSGI request. | 325 """Represents a response to a WSGI request. |
295 | 326 |
296 A response consists of a status line, headers, and a body. | 327 A response consists of a status line, headers, and a body. |
297 | 328 |