[wsgi] make sure request.content is available for consumption

The wsgi.input stream is not seekable, so make a copy either to memory or disk so it's still available to the user even after we've parsed it (for POST form processing). Closes #3554996

authorJulien Cristau <julien.cristau@logilab.fr>
changeset48f0ff3e2a32
branchdefault
phasepublic
hiddenno
parent revision#0509880fec01 [facet] create a RangeRQLPathFacet (closes #2852512)
child revision#7fb3c032560e [wsgi] avoid reading the entire request body in memory, #88adf9a5736f [source/native] refactor eid generation code, #7b63e5cd0400 [devtools] add a 'method' argument to RepoAccess.web_request, #b87c09f853d3 [doc] undocument user_callback
files modified by this revision
wsgi/request.py
# HG changeset patch
# User Julien Cristau <julien.cristau@logilab.fr>
# Date 1394110764 -3600
# Thu Mar 06 13:59:24 2014 +0100
# Node ID 48f0ff3e2a32982ab6881ff81e6561079f13762b
# Parent 0509880fec01fe34b6fd2514d1dcdb4b58244da4
[wsgi] make sure request.content is available for consumption

The wsgi.input stream is not seekable, so make a copy either to memory
or disk so it's still available to the user even after we've parsed it
(for POST form processing). Closes #3554996

diff --git a/wsgi/request.py b/wsgi/request.py
@@ -43,11 +43,21 @@
1 
2      def __init__(self, environ, vreg):
3          self.environ = environ
4          self.path = environ['PATH_INFO']
5          self.method = environ['REQUEST_METHOD'].upper()
6 -        self.content = environ['wsgi.input']
7 +        try:
8 +            length = int(environ['CONTENT_LENGTH'])
9 +        except (KeyError, ValueError):
10 +            length = 0
11 +        # wsgi.input is not seekable, so copy the request contents to a temporary file
12 +        if length < 100000:
13 +            self.content = StringIO()
14 +        else:
15 +            self.content = tempfile.TemporaryFile()
16 +        safe_copyfileobj(environ['wsgi.input'], self.content, size=length)
17 +        self.content.seek(0, 0)
18 
19          headers_in = dict((normalize_header(k[5:]), v) for k, v in self.environ.items()
20                            if k.startswith('HTTP_'))
21          https = environ.get("HTTPS") in ('yes', 'on', '1')
22          post, files = self.get_posted_data()
@@ -137,17 +147,8 @@
23          return post, files
24 
25      @property
26      @cached
27      def raw_post_data(self):
28 -        buf = StringIO()
29 -        try:
30 -            # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
31 -            content_length = int(self.environ.get('CONTENT_LENGTH', 0))
32 -        except ValueError: # if CONTENT_LENGTH was empty string or not an integer
33 -            content_length = 0
34 -        if content_length > 0:
35 -            safe_copyfileobj(self.environ['wsgi.input'], buf,
36 -                    size=content_length)
37 -        postdata = buf.getvalue()
38 -        buf.close()
39 +        postdata = self.content.read()
40 +        self.content.seek(0, 0)
41          return postdata