##// END OF EJS Templates
hgweb: clarify that apppath begins with a forward slash...
Gregory Szorc -
r36915:e67a2e05 default
parent child Browse files
Show More
@@ -1,668 +1,669 b''
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import socket
13 13 import wsgiref.headers as wsgiheaders
14 14 #import wsgiref.validate
15 15
16 16 from .common import (
17 17 ErrorResponse,
18 18 statusmessage,
19 19 )
20 20
21 21 from ..thirdparty import (
22 22 attr,
23 23 )
24 24 from .. import (
25 25 error,
26 26 pycompat,
27 27 util,
28 28 )
29 29
30 30 class multidict(object):
31 31 """A dict like object that can store multiple values for a key.
32 32
33 33 Used to store parsed request parameters.
34 34
35 35 This is inspired by WebOb's class of the same name.
36 36 """
37 37 def __init__(self):
38 38 # Stores (key, value) 2-tuples. This isn't the most efficient. But we
39 39 # don't rely on parameters that much, so it shouldn't be a perf issue.
40 40 # we can always add dict for fast lookups.
41 41 self._items = []
42 42
43 43 def __getitem__(self, key):
44 44 """Returns the last set value for a key."""
45 45 for k, v in reversed(self._items):
46 46 if k == key:
47 47 return v
48 48
49 49 raise KeyError(key)
50 50
51 51 def __setitem__(self, key, value):
52 52 """Replace a values for a key with a new value."""
53 53 try:
54 54 del self[key]
55 55 except KeyError:
56 56 pass
57 57
58 58 self._items.append((key, value))
59 59
60 60 def __delitem__(self, key):
61 61 """Delete all values for a key."""
62 62 oldlen = len(self._items)
63 63
64 64 self._items[:] = [(k, v) for k, v in self._items if k != key]
65 65
66 66 if oldlen == len(self._items):
67 67 raise KeyError(key)
68 68
69 69 def __contains__(self, key):
70 70 return any(k == key for k, v in self._items)
71 71
72 72 def __len__(self):
73 73 return len(self._items)
74 74
75 75 def get(self, key, default=None):
76 76 try:
77 77 return self.__getitem__(key)
78 78 except KeyError:
79 79 return default
80 80
81 81 def add(self, key, value):
82 82 """Add a new value for a key. Does not replace existing values."""
83 83 self._items.append((key, value))
84 84
85 85 def getall(self, key):
86 86 """Obtains all values for a key."""
87 87 return [v for k, v in self._items if k == key]
88 88
89 89 def getone(self, key):
90 90 """Obtain a single value for a key.
91 91
92 92 Raises KeyError if key not defined or it has multiple values set.
93 93 """
94 94 vals = self.getall(key)
95 95
96 96 if not vals:
97 97 raise KeyError(key)
98 98
99 99 if len(vals) > 1:
100 100 raise KeyError('multiple values for %r' % key)
101 101
102 102 return vals[0]
103 103
104 104 def asdictoflists(self):
105 105 d = {}
106 106 for k, v in self._items:
107 107 if k in d:
108 108 d[k].append(v)
109 109 else:
110 110 d[k] = [v]
111 111
112 112 return d
113 113
114 114 @attr.s(frozen=True)
115 115 class parsedrequest(object):
116 116 """Represents a parsed WSGI request.
117 117
118 118 Contains both parsed parameters as well as a handle on the input stream.
119 119 """
120 120
121 121 # Request method.
122 122 method = attr.ib()
123 123 # Full URL for this request.
124 124 url = attr.ib()
125 125 # URL without any path components. Just <proto>://<host><port>.
126 126 baseurl = attr.ib()
127 127 # Advertised URL. Like ``url`` and ``baseurl`` but uses SERVER_NAME instead
128 128 # of HTTP: Host header for hostname. This is likely what clients used.
129 129 advertisedurl = attr.ib()
130 130 advertisedbaseurl = attr.ib()
131 131 # URL scheme (part before ``://``). e.g. ``http`` or ``https``.
132 132 urlscheme = attr.ib()
133 133 # Value of REMOTE_USER, if set, or None.
134 134 remoteuser = attr.ib()
135 135 # Value of REMOTE_HOST, if set, or None.
136 136 remotehost = attr.ib()
137 # WSGI application path.
137 # Relative WSGI application path. If defined, will begin with a
138 # ``/``.
138 139 apppath = attr.ib()
139 140 # List of path parts to be used for dispatch.
140 141 dispatchparts = attr.ib()
141 142 # URL path component (no query string) used for dispatch. Can be
142 143 # ``None`` to signal no path component given to the request, an
143 144 # empty string to signal a request to the application's root URL,
144 145 # or a string not beginning with ``/`` containing the requested
145 146 # path under the application.
146 147 dispatchpath = attr.ib()
147 148 # The name of the repository being accessed.
148 149 reponame = attr.ib()
149 150 # Raw query string (part after "?" in URL).
150 151 querystring = attr.ib()
151 152 # multidict of query string parameters.
152 153 qsparams = attr.ib()
153 154 # wsgiref.headers.Headers instance. Operates like a dict with case
154 155 # insensitive keys.
155 156 headers = attr.ib()
156 157 # Request body input stream.
157 158 bodyfh = attr.ib()
158 159
159 160 def parserequestfromenv(env, bodyfh, reponame=None):
160 161 """Parse URL components from environment variables.
161 162
162 163 WSGI defines request attributes via environment variables. This function
163 164 parses the environment variables into a data structure.
164 165
165 166 If ``reponame`` is defined, the leading path components matching that
166 167 string are effectively shifted from ``PATH_INFO`` to ``SCRIPT_NAME``.
167 168 This simulates the world view of a WSGI application that processes
168 169 requests from the base URL of a repo.
169 170 """
170 171 # PEP-0333 defines the WSGI spec and is a useful reference for this code.
171 172
172 173 # We first validate that the incoming object conforms with the WSGI spec.
173 174 # We only want to be dealing with spec-conforming WSGI implementations.
174 175 # TODO enable this once we fix internal violations.
175 176 #wsgiref.validate.check_environ(env)
176 177
177 178 # PEP-0333 states that environment keys and values are native strings
178 179 # (bytes on Python 2 and str on Python 3). The code points for the Unicode
179 180 # strings on Python 3 must be between \00000-\000FF. We deal with bytes
180 181 # in Mercurial, so mass convert string keys and values to bytes.
181 182 if pycompat.ispy3:
182 183 env = {k.encode('latin-1'): v for k, v in env.iteritems()}
183 184 env = {k: v.encode('latin-1') if isinstance(v, str) else v
184 185 for k, v in env.iteritems()}
185 186
186 187 # https://www.python.org/dev/peps/pep-0333/#environ-variables defines
187 188 # the environment variables.
188 189 # https://www.python.org/dev/peps/pep-0333/#url-reconstruction defines
189 190 # how URLs are reconstructed.
190 191 fullurl = env['wsgi.url_scheme'] + '://'
191 192 advertisedfullurl = fullurl
192 193
193 194 def addport(s):
194 195 if env['wsgi.url_scheme'] == 'https':
195 196 if env['SERVER_PORT'] != '443':
196 197 s += ':' + env['SERVER_PORT']
197 198 else:
198 199 if env['SERVER_PORT'] != '80':
199 200 s += ':' + env['SERVER_PORT']
200 201
201 202 return s
202 203
203 204 if env.get('HTTP_HOST'):
204 205 fullurl += env['HTTP_HOST']
205 206 else:
206 207 fullurl += env['SERVER_NAME']
207 208 fullurl = addport(fullurl)
208 209
209 210 advertisedfullurl += env['SERVER_NAME']
210 211 advertisedfullurl = addport(advertisedfullurl)
211 212
212 213 baseurl = fullurl
213 214 advertisedbaseurl = advertisedfullurl
214 215
215 216 fullurl += util.urlreq.quote(env.get('SCRIPT_NAME', ''))
216 217 advertisedfullurl += util.urlreq.quote(env.get('SCRIPT_NAME', ''))
217 218 fullurl += util.urlreq.quote(env.get('PATH_INFO', ''))
218 219 advertisedfullurl += util.urlreq.quote(env.get('PATH_INFO', ''))
219 220
220 221 if env.get('QUERY_STRING'):
221 222 fullurl += '?' + env['QUERY_STRING']
222 223 advertisedfullurl += '?' + env['QUERY_STRING']
223 224
224 225 # If ``reponame`` is defined, that must be a prefix on PATH_INFO
225 226 # that represents the repository being dispatched to. When computing
226 227 # the dispatch info, we ignore these leading path components.
227 228
228 229 apppath = env.get('SCRIPT_NAME', '')
229 230
230 231 if reponame:
231 232 repoprefix = '/' + reponame.strip('/')
232 233
233 234 if not env.get('PATH_INFO'):
234 235 raise error.ProgrammingError('reponame requires PATH_INFO')
235 236
236 237 if not env['PATH_INFO'].startswith(repoprefix):
237 238 raise error.ProgrammingError('PATH_INFO does not begin with repo '
238 239 'name: %s (%s)' % (env['PATH_INFO'],
239 240 reponame))
240 241
241 242 dispatchpath = env['PATH_INFO'][len(repoprefix):]
242 243
243 244 if dispatchpath and not dispatchpath.startswith('/'):
244 245 raise error.ProgrammingError('reponame prefix of PATH_INFO does '
245 246 'not end at path delimiter: %s (%s)' %
246 247 (env['PATH_INFO'], reponame))
247 248
248 249 apppath = apppath.rstrip('/') + repoprefix
249 250 dispatchparts = dispatchpath.strip('/').split('/')
250 251 dispatchpath = '/'.join(dispatchparts)
251 252
252 253 elif 'PATH_INFO' in env:
253 254 if env['PATH_INFO'].strip('/'):
254 255 dispatchparts = env['PATH_INFO'].strip('/').split('/')
255 256 dispatchpath = '/'.join(dispatchparts)
256 257 else:
257 258 dispatchparts = []
258 259 dispatchpath = ''
259 260 else:
260 261 dispatchparts = []
261 262 dispatchpath = None
262 263
263 264 querystring = env.get('QUERY_STRING', '')
264 265
265 266 # We store as a list so we have ordering information. We also store as
266 267 # a dict to facilitate fast lookup.
267 268 qsparams = multidict()
268 269 for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True):
269 270 qsparams.add(k, v)
270 271
271 272 # HTTP_* keys contain HTTP request headers. The Headers structure should
272 273 # perform case normalization for us. We just rewrite underscore to dash
273 274 # so keys match what likely went over the wire.
274 275 headers = []
275 276 for k, v in env.iteritems():
276 277 if k.startswith('HTTP_'):
277 278 headers.append((k[len('HTTP_'):].replace('_', '-'), v))
278 279
279 280 headers = wsgiheaders.Headers(headers)
280 281
281 282 # This is kind of a lie because the HTTP header wasn't explicitly
282 283 # sent. But for all intents and purposes it should be OK to lie about
283 284 # this, since a consumer will either either value to determine how many
284 285 # bytes are available to read.
285 286 if 'CONTENT_LENGTH' in env and 'HTTP_CONTENT_LENGTH' not in env:
286 287 headers['Content-Length'] = env['CONTENT_LENGTH']
287 288
288 289 # TODO do this once we remove wsgirequest.inp, otherwise we could have
289 290 # multiple readers from the underlying input stream.
290 291 #bodyfh = env['wsgi.input']
291 292 #if 'Content-Length' in headers:
292 293 # bodyfh = util.cappedreader(bodyfh, int(headers['Content-Length']))
293 294
294 295 return parsedrequest(method=env['REQUEST_METHOD'],
295 296 url=fullurl, baseurl=baseurl,
296 297 advertisedurl=advertisedfullurl,
297 298 advertisedbaseurl=advertisedbaseurl,
298 299 urlscheme=env['wsgi.url_scheme'],
299 300 remoteuser=env.get('REMOTE_USER'),
300 301 remotehost=env.get('REMOTE_HOST'),
301 302 apppath=apppath,
302 303 dispatchparts=dispatchparts, dispatchpath=dispatchpath,
303 304 reponame=reponame,
304 305 querystring=querystring,
305 306 qsparams=qsparams,
306 307 headers=headers,
307 308 bodyfh=bodyfh)
308 309
309 310 class offsettrackingwriter(object):
310 311 """A file object like object that is append only and tracks write count.
311 312
312 313 Instances are bound to a callable. This callable is called with data
313 314 whenever a ``write()`` is attempted.
314 315
315 316 Instances track the amount of written data so they can answer ``tell()``
316 317 requests.
317 318
318 319 The intent of this class is to wrap the ``write()`` function returned by
319 320 a WSGI ``start_response()`` function. Since ``write()`` is a callable and
320 321 not a file object, it doesn't implement other file object methods.
321 322 """
322 323 def __init__(self, writefn):
323 324 self._write = writefn
324 325 self._offset = 0
325 326
326 327 def write(self, s):
327 328 res = self._write(s)
328 329 # Some Python objects don't report the number of bytes written.
329 330 if res is None:
330 331 self._offset += len(s)
331 332 else:
332 333 self._offset += res
333 334
334 335 def flush(self):
335 336 pass
336 337
337 338 def tell(self):
338 339 return self._offset
339 340
340 341 class wsgiresponse(object):
341 342 """Represents a response to a WSGI request.
342 343
343 344 A response consists of a status line, headers, and a body.
344 345
345 346 Consumers must populate the ``status`` and ``headers`` fields and
346 347 make a call to a ``setbody*()`` method before the response can be
347 348 issued.
348 349
349 350 When it is time to start sending the response over the wire,
350 351 ``sendresponse()`` is called. It handles emitting the header portion
351 352 of the response message. It then yields chunks of body data to be
352 353 written to the peer. Typically, the WSGI application itself calls
353 354 and returns the value from ``sendresponse()``.
354 355 """
355 356
356 357 def __init__(self, req, startresponse):
357 358 """Create an empty response tied to a specific request.
358 359
359 360 ``req`` is a ``parsedrequest``. ``startresponse`` is the
360 361 ``start_response`` function passed to the WSGI application.
361 362 """
362 363 self._req = req
363 364 self._startresponse = startresponse
364 365
365 366 self.status = None
366 367 self.headers = wsgiheaders.Headers([])
367 368
368 369 self._bodybytes = None
369 370 self._bodygen = None
370 371 self._bodywillwrite = False
371 372 self._started = False
372 373 self._bodywritefn = None
373 374
374 375 def _verifybody(self):
375 376 if (self._bodybytes is not None or self._bodygen is not None
376 377 or self._bodywillwrite):
377 378 raise error.ProgrammingError('cannot define body multiple times')
378 379
379 380 def setbodybytes(self, b):
380 381 """Define the response body as static bytes.
381 382
382 383 The empty string signals that there is no response body.
383 384 """
384 385 self._verifybody()
385 386 self._bodybytes = b
386 387 self.headers['Content-Length'] = '%d' % len(b)
387 388
388 389 def setbodygen(self, gen):
389 390 """Define the response body as a generator of bytes."""
390 391 self._verifybody()
391 392 self._bodygen = gen
392 393
393 394 def setbodywillwrite(self):
394 395 """Signal an intent to use write() to emit the response body.
395 396
396 397 **This is the least preferred way to send a body.**
397 398
398 399 It is preferred for WSGI applications to emit a generator of chunks
399 400 constituting the response body. However, some consumers can't emit
400 401 data this way. So, WSGI provides a way to obtain a ``write(data)``
401 402 function that can be used to synchronously perform an unbuffered
402 403 write.
403 404
404 405 Calling this function signals an intent to produce the body in this
405 406 manner.
406 407 """
407 408 self._verifybody()
408 409 self._bodywillwrite = True
409 410
410 411 def sendresponse(self):
411 412 """Send the generated response to the client.
412 413
413 414 Before this is called, ``status`` must be set and one of
414 415 ``setbodybytes()`` or ``setbodygen()`` must be called.
415 416
416 417 Calling this method multiple times is not allowed.
417 418 """
418 419 if self._started:
419 420 raise error.ProgrammingError('sendresponse() called multiple times')
420 421
421 422 self._started = True
422 423
423 424 if not self.status:
424 425 raise error.ProgrammingError('status line not defined')
425 426
426 427 if (self._bodybytes is None and self._bodygen is None
427 428 and not self._bodywillwrite):
428 429 raise error.ProgrammingError('response body not defined')
429 430
430 431 # RFC 7232 Section 4.1 states that a 304 MUST generate one of
431 432 # {Cache-Control, Content-Location, Date, ETag, Expires, Vary}
432 433 # and SHOULD NOT generate other headers unless they could be used
433 434 # to guide cache updates. Furthermore, RFC 7230 Section 3.3.2
434 435 # states that no response body can be issued. Content-Length can
435 436 # be sent. But if it is present, it should be the size of the response
436 437 # that wasn't transferred.
437 438 if self.status.startswith('304 '):
438 439 # setbodybytes('') will set C-L to 0. This doesn't conform with the
439 440 # spec. So remove it.
440 441 if self.headers.get('Content-Length') == '0':
441 442 del self.headers['Content-Length']
442 443
443 444 # Strictly speaking, this is too strict. But until it causes
444 445 # problems, let's be strict.
445 446 badheaders = {k for k in self.headers.keys()
446 447 if k.lower() not in ('date', 'etag', 'expires',
447 448 'cache-control',
448 449 'content-location',
449 450 'vary')}
450 451 if badheaders:
451 452 raise error.ProgrammingError(
452 453 'illegal header on 304 response: %s' %
453 454 ', '.join(sorted(badheaders)))
454 455
455 456 if self._bodygen is not None or self._bodywillwrite:
456 457 raise error.ProgrammingError("must use setbodybytes('') with "
457 458 "304 responses")
458 459
459 460 # Various HTTP clients (notably httplib) won't read the HTTP response
460 461 # until the HTTP request has been sent in full. If servers (us) send a
461 462 # response before the HTTP request has been fully sent, the connection
462 463 # may deadlock because neither end is reading.
463 464 #
464 465 # We work around this by "draining" the request data before
465 466 # sending any response in some conditions.
466 467 drain = False
467 468 close = False
468 469
469 470 # If the client sent Expect: 100-continue, we assume it is smart enough
470 471 # to deal with the server sending a response before reading the request.
471 472 # (httplib doesn't do this.)
472 473 if self._req.headers.get('Expect', '').lower() == '100-continue':
473 474 pass
474 475 # Only tend to request methods that have bodies. Strictly speaking,
475 476 # we should sniff for a body. But this is fine for our existing
476 477 # WSGI applications.
477 478 elif self._req.method not in ('POST', 'PUT'):
478 479 pass
479 480 else:
480 481 # If we don't know how much data to read, there's no guarantee
481 482 # that we can drain the request responsibly. The WSGI
482 483 # specification only says that servers *should* ensure the
483 484 # input stream doesn't overrun the actual request. So there's
484 485 # no guarantee that reading until EOF won't corrupt the stream
485 486 # state.
486 487 if not isinstance(self._req.bodyfh, util.cappedreader):
487 488 close = True
488 489 else:
489 490 # We /could/ only drain certain HTTP response codes. But 200 and
490 491 # non-200 wire protocol responses both require draining. Since
491 492 # we have a capped reader in place for all situations where we
492 493 # drain, it is safe to read from that stream. We'll either do
493 494 # a drain or no-op if we're already at EOF.
494 495 drain = True
495 496
496 497 if close:
497 498 self.headers['Connection'] = 'Close'
498 499
499 500 if drain:
500 501 assert isinstance(self._req.bodyfh, util.cappedreader)
501 502 while True:
502 503 chunk = self._req.bodyfh.read(32768)
503 504 if not chunk:
504 505 break
505 506
506 507 write = self._startresponse(pycompat.sysstr(self.status),
507 508 self.headers.items())
508 509
509 510 if self._bodybytes:
510 511 yield self._bodybytes
511 512 elif self._bodygen:
512 513 for chunk in self._bodygen:
513 514 yield chunk
514 515 elif self._bodywillwrite:
515 516 self._bodywritefn = write
516 517 else:
517 518 error.ProgrammingError('do not know how to send body')
518 519
519 520 def getbodyfile(self):
520 521 """Obtain a file object like object representing the response body.
521 522
522 523 For this to work, you must call ``setbodywillwrite()`` and then
523 524 ``sendresponse()`` first. ``sendresponse()`` is a generator and the
524 525 function won't run to completion unless the generator is advanced. The
525 526 generator yields not items. The easiest way to consume it is with
526 527 ``list(res.sendresponse())``, which should resolve to an empty list -
527 528 ``[]``.
528 529 """
529 530 if not self._bodywillwrite:
530 531 raise error.ProgrammingError('must call setbodywillwrite() first')
531 532
532 533 if not self._started:
533 534 raise error.ProgrammingError('must call sendresponse() first; did '
534 535 'you remember to consume it since it '
535 536 'is a generator?')
536 537
537 538 assert self._bodywritefn
538 539 return offsettrackingwriter(self._bodywritefn)
539 540
540 541 class wsgirequest(object):
541 542 """Higher-level API for a WSGI request.
542 543
543 544 WSGI applications are invoked with 2 arguments. They are used to
544 545 instantiate instances of this class, which provides higher-level APIs
545 546 for obtaining request parameters, writing HTTP output, etc.
546 547 """
547 548 def __init__(self, wsgienv, start_response):
548 549 version = wsgienv[r'wsgi.version']
549 550 if (version < (1, 0)) or (version >= (2, 0)):
550 551 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
551 552 % version)
552 553
553 554 inp = wsgienv[r'wsgi.input']
554 555
555 556 if r'HTTP_CONTENT_LENGTH' in wsgienv:
556 557 inp = util.cappedreader(inp, int(wsgienv[r'HTTP_CONTENT_LENGTH']))
557 558 elif r'CONTENT_LENGTH' in wsgienv:
558 559 inp = util.cappedreader(inp, int(wsgienv[r'CONTENT_LENGTH']))
559 560
560 561 self.err = wsgienv[r'wsgi.errors']
561 562 self.threaded = wsgienv[r'wsgi.multithread']
562 563 self.multiprocess = wsgienv[r'wsgi.multiprocess']
563 564 self.run_once = wsgienv[r'wsgi.run_once']
564 565 self.env = wsgienv
565 566 self.req = parserequestfromenv(wsgienv, inp)
566 567 self.res = wsgiresponse(self.req, start_response)
567 568 self._start_response = start_response
568 569 self.server_write = None
569 570 self.headers = []
570 571
571 572 def respond(self, status, type, filename=None, body=None):
572 573 if not isinstance(type, str):
573 574 type = pycompat.sysstr(type)
574 575 if self._start_response is not None:
575 576 self.headers.append((r'Content-Type', type))
576 577 if filename:
577 578 filename = (filename.rpartition('/')[-1]
578 579 .replace('\\', '\\\\').replace('"', '\\"'))
579 580 self.headers.append(('Content-Disposition',
580 581 'inline; filename="%s"' % filename))
581 582 if body is not None:
582 583 self.headers.append((r'Content-Length', str(len(body))))
583 584
584 585 for k, v in self.headers:
585 586 if not isinstance(v, str):
586 587 raise TypeError('header value must be string: %r' % (v,))
587 588
588 589 if isinstance(status, ErrorResponse):
589 590 self.headers.extend(status.headers)
590 591 status = statusmessage(status.code, pycompat.bytestr(status))
591 592 elif status == 200:
592 593 status = '200 Script output follows'
593 594 elif isinstance(status, int):
594 595 status = statusmessage(status)
595 596
596 597 # Various HTTP clients (notably httplib) won't read the HTTP
597 598 # response until the HTTP request has been sent in full. If servers
598 599 # (us) send a response before the HTTP request has been fully sent,
599 600 # the connection may deadlock because neither end is reading.
600 601 #
601 602 # We work around this by "draining" the request data before
602 603 # sending any response in some conditions.
603 604 drain = False
604 605 close = False
605 606
606 607 # If the client sent Expect: 100-continue, we assume it is smart
607 608 # enough to deal with the server sending a response before reading
608 609 # the request. (httplib doesn't do this.)
609 610 if self.env.get(r'HTTP_EXPECT', r'').lower() == r'100-continue':
610 611 pass
611 612 # Only tend to request methods that have bodies. Strictly speaking,
612 613 # we should sniff for a body. But this is fine for our existing
613 614 # WSGI applications.
614 615 elif self.env[r'REQUEST_METHOD'] not in (r'POST', r'PUT'):
615 616 pass
616 617 else:
617 618 # If we don't know how much data to read, there's no guarantee
618 619 # that we can drain the request responsibly. The WSGI
619 620 # specification only says that servers *should* ensure the
620 621 # input stream doesn't overrun the actual request. So there's
621 622 # no guarantee that reading until EOF won't corrupt the stream
622 623 # state.
623 624 if not isinstance(self.req.bodyfh, util.cappedreader):
624 625 close = True
625 626 else:
626 627 # We /could/ only drain certain HTTP response codes. But 200
627 628 # and non-200 wire protocol responses both require draining.
628 629 # Since we have a capped reader in place for all situations
629 630 # where we drain, it is safe to read from that stream. We'll
630 631 # either do a drain or no-op if we're already at EOF.
631 632 drain = True
632 633
633 634 if close:
634 635 self.headers.append((r'Connection', r'Close'))
635 636
636 637 if drain:
637 638 assert isinstance(self.req.bodyfh, util.cappedreader)
638 639 while True:
639 640 chunk = self.req.bodyfh.read(32768)
640 641 if not chunk:
641 642 break
642 643
643 644 self.server_write = self._start_response(
644 645 pycompat.sysstr(status), self.headers)
645 646 self._start_response = None
646 647 self.headers = []
647 648 if body is not None:
648 649 self.write(body)
649 650 self.server_write = None
650 651
651 652 def write(self, thing):
652 653 if thing:
653 654 try:
654 655 self.server_write(thing)
655 656 except socket.error as inst:
656 657 if inst[0] != errno.ECONNRESET:
657 658 raise
658 659
659 660 def flush(self):
660 661 return None
661 662
662 663 def wsgiapplication(app_maker):
663 664 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
664 665 can and should now be used as a WSGI application.'''
665 666 application = app_maker()
666 667 def run_wsgi(env, respond):
667 668 return application(env, respond)
668 669 return run_wsgi
General Comments 0
You need to be logged in to leave comments. Login now