##// END OF EJS Templates
hgweb: expose input stream on parsed WSGI request object...
Gregory Szorc -
r36873:da4e2f87 default
parent child Browse files
Show More
@@ -291,7 +291,8 b' class hgwebdir(object):'
291 # variable.
291 # variable.
292 # TODO this is kind of hacky and we should have a better
292 # TODO this is kind of hacky and we should have a better
293 # way of doing this than with REPO_NAME side-effects.
293 # way of doing this than with REPO_NAME side-effects.
294 wsgireq.req = requestmod.parserequestfromenv(wsgireq.env)
294 wsgireq.req = requestmod.parserequestfromenv(
295 wsgireq.env, wsgireq.req.bodyfh)
295 try:
296 try:
296 # ensure caller gets private copy of ui
297 # ensure caller gets private copy of ui
297 repo = hg.repository(self.ui.copy(), real)
298 repo = hg.repository(self.ui.copy(), real)
@@ -61,7 +61,10 b' def normalize(form):'
61
61
62 @attr.s(frozen=True)
62 @attr.s(frozen=True)
63 class parsedrequest(object):
63 class parsedrequest(object):
64 """Represents a parsed WSGI request / static HTTP request parameters."""
64 """Represents a parsed WSGI request.
65
66 Contains both parsed parameters as well as a handle on the input stream.
67 """
65
68
66 # Request method.
69 # Request method.
67 method = attr.ib()
70 method = attr.ib()
@@ -91,8 +94,10 b' class parsedrequest(object):'
91 # wsgiref.headers.Headers instance. Operates like a dict with case
94 # wsgiref.headers.Headers instance. Operates like a dict with case
92 # insensitive keys.
95 # insensitive keys.
93 headers = attr.ib()
96 headers = attr.ib()
97 # Request body input stream.
98 bodyfh = attr.ib()
94
99
95 def parserequestfromenv(env):
100 def parserequestfromenv(env, bodyfh):
96 """Parse URL components from environment variables.
101 """Parse URL components from environment variables.
97
102
98 WSGI defines request attributes via environment variables. This function
103 WSGI defines request attributes via environment variables. This function
@@ -209,6 +214,12 b' def parserequestfromenv(env):'
209 if 'CONTENT_LENGTH' in env and 'HTTP_CONTENT_LENGTH' not in env:
214 if 'CONTENT_LENGTH' in env and 'HTTP_CONTENT_LENGTH' not in env:
210 headers['Content-Length'] = env['CONTENT_LENGTH']
215 headers['Content-Length'] = env['CONTENT_LENGTH']
211
216
217 # TODO do this once we remove wsgirequest.inp, otherwise we could have
218 # multiple readers from the underlying input stream.
219 #bodyfh = env['wsgi.input']
220 #if 'Content-Length' in headers:
221 # bodyfh = util.cappedreader(bodyfh, int(headers['Content-Length']))
222
212 return parsedrequest(method=env['REQUEST_METHOD'],
223 return parsedrequest(method=env['REQUEST_METHOD'],
213 url=fullurl, baseurl=baseurl,
224 url=fullurl, baseurl=baseurl,
214 advertisedurl=advertisedfullurl,
225 advertisedurl=advertisedfullurl,
@@ -219,7 +230,8 b' def parserequestfromenv(env):'
219 querystring=querystring,
230 querystring=querystring,
220 querystringlist=querystringlist,
231 querystringlist=querystringlist,
221 querystringdict=querystringdict,
232 querystringdict=querystringdict,
222 headers=headers)
233 headers=headers,
234 bodyfh=bodyfh)
223
235
224 class wsgirequest(object):
236 class wsgirequest(object):
225 """Higher-level API for a WSGI request.
237 """Higher-level API for a WSGI request.
@@ -233,28 +245,27 b' class wsgirequest(object):'
233 if (version < (1, 0)) or (version >= (2, 0)):
245 if (version < (1, 0)) or (version >= (2, 0)):
234 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
246 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
235 % version)
247 % version)
236 self.inp = wsgienv[r'wsgi.input']
248
249 inp = wsgienv[r'wsgi.input']
237
250
238 if r'HTTP_CONTENT_LENGTH' in wsgienv:
251 if r'HTTP_CONTENT_LENGTH' in wsgienv:
239 self.inp = util.cappedreader(self.inp,
252 inp = util.cappedreader(inp, int(wsgienv[r'HTTP_CONTENT_LENGTH']))
240 int(wsgienv[r'HTTP_CONTENT_LENGTH']))
241 elif r'CONTENT_LENGTH' in wsgienv:
253 elif r'CONTENT_LENGTH' in wsgienv:
242 self.inp = util.cappedreader(self.inp,
254 inp = util.cappedreader(inp, int(wsgienv[r'CONTENT_LENGTH']))
243 int(wsgienv[r'CONTENT_LENGTH']))
244
255
245 self.err = wsgienv[r'wsgi.errors']
256 self.err = wsgienv[r'wsgi.errors']
246 self.threaded = wsgienv[r'wsgi.multithread']
257 self.threaded = wsgienv[r'wsgi.multithread']
247 self.multiprocess = wsgienv[r'wsgi.multiprocess']
258 self.multiprocess = wsgienv[r'wsgi.multiprocess']
248 self.run_once = wsgienv[r'wsgi.run_once']
259 self.run_once = wsgienv[r'wsgi.run_once']
249 self.env = wsgienv
260 self.env = wsgienv
250 self.form = normalize(cgi.parse(self.inp,
261 self.form = normalize(cgi.parse(inp,
251 self.env,
262 self.env,
252 keep_blank_values=1))
263 keep_blank_values=1))
253 self._start_response = start_response
264 self._start_response = start_response
254 self.server_write = None
265 self.server_write = None
255 self.headers = []
266 self.headers = []
256
267
257 self.req = parserequestfromenv(wsgienv)
268 self.req = parserequestfromenv(wsgienv, inp)
258
269
259 def respond(self, status, type, filename=None, body=None):
270 def respond(self, status, type, filename=None, body=None):
260 if not isinstance(type, str):
271 if not isinstance(type, str):
@@ -315,7 +326,7 b' class wsgirequest(object):'
315 # input stream doesn't overrun the actual request. So there's
326 # input stream doesn't overrun the actual request. So there's
316 # no guarantee that reading until EOF won't corrupt the stream
327 # no guarantee that reading until EOF won't corrupt the stream
317 # state.
328 # state.
318 if not isinstance(self.inp, util.cappedreader):
329 if not isinstance(self.req.bodyfh, util.cappedreader):
319 close = True
330 close = True
320 else:
331 else:
321 # We /could/ only drain certain HTTP response codes. But 200
332 # We /could/ only drain certain HTTP response codes. But 200
@@ -329,9 +340,9 b' class wsgirequest(object):'
329 self.headers.append((r'Connection', r'Close'))
340 self.headers.append((r'Connection', r'Close'))
330
341
331 if drain:
342 if drain:
332 assert isinstance(self.inp, util.cappedreader)
343 assert isinstance(self.req.bodyfh, util.cappedreader)
333 while True:
344 while True:
334 chunk = self.inp.read(32768)
345 chunk = self.req.bodyfh.read(32768)
335 if not chunk:
346 if not chunk:
336 break
347 break
337
348
@@ -83,7 +83,7 b' class httpv1protocolhandler(wireprototyp'
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
84 if postlen:
84 if postlen:
85 args.update(urlreq.parseqs(
85 args.update(urlreq.parseqs(
86 self._wsgireq.inp.read(postlen), keep_blank_values=True))
86 self._req.bodyfh.read(postlen), keep_blank_values=True))
87 return args
87 return args
88
88
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
@@ -97,7 +97,7 b' class httpv1protocolhandler(wireprototyp'
97 # If httppostargs is used, we need to read Content-Length
97 # If httppostargs is used, we need to read Content-Length
98 # minus the amount that was consumed by args.
98 # minus the amount that was consumed by args.
99 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
100 for s in util.filechunkiter(self._wsgireq.inp, limit=length):
100 for s in util.filechunkiter(self._req.bodyfh, limit=length):
101 fp.write(s)
101 fp.write(s)
102
102
103 @contextlib.contextmanager
103 @contextlib.contextmanager
General Comments 0
You need to be logged in to leave comments. Login now