request.py
147 lines
| 5.3 KiB
| text/x-python
|
PythonLexer
Eric Hopper
|
r2391 | # hgweb/request.py - An http request from either CGI or the standalone server. | ||
Eric Hopper
|
r2355 | # | ||
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> | ||||
Vadim Gelfer
|
r2859 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> | ||
Eric Hopper
|
r2355 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Eric Hopper
|
r2355 | |||
Benoit Boissinot
|
r3963 | import socket, cgi, errno | ||
Dirkjan Ochtman
|
r7180 | from mercurial import util | ||
Augie Fackler
|
r12739 | from common import ErrorResponse, statusmessage, HTTP_NOT_MODIFIED | ||
Eric Hopper
|
r2355 | |||
Dirkjan Ochtman
|
r6774 | shortcuts = { | ||
'cl': [('cmd', ['changelog']), ('rev', None)], | ||||
'sl': [('cmd', ['shortlog']), ('rev', None)], | ||||
'cs': [('cmd', ['changeset']), ('node', None)], | ||||
'f': [('cmd', ['file']), ('filenode', None)], | ||||
'fl': [('cmd', ['filelog']), ('filenode', None)], | ||||
'fd': [('cmd', ['filediff']), ('node', None)], | ||||
'fa': [('cmd', ['annotate']), ('filenode', None)], | ||||
'mf': [('cmd', ['manifest']), ('manifest', None)], | ||||
'ca': [('cmd', ['archive']), ('node', None)], | ||||
'tags': [('cmd', ['tags'])], | ||||
'tip': [('cmd', ['changeset']), ('node', ['tip'])], | ||||
'static': [('cmd', ['static']), ('file', None)] | ||||
} | ||||
Nicolas Dumazet
|
r10261 | def normalize(form): | ||
# first expand the shortcuts | ||||
Dirkjan Ochtman
|
r6774 | for k in shortcuts.iterkeys(): | ||
if k in form: | ||||
for name, value in shortcuts[k]: | ||||
if value is None: | ||||
value = form[k] | ||||
form[name] = value | ||||
del form[k] | ||||
Nicolas Dumazet
|
r10261 | # And strip the values | ||
for k, v in form.iteritems(): | ||||
form[k] = [i.strip() for i in v] | ||||
Dirkjan Ochtman
|
r6774 | return form | ||
Dirkjan Ochtman
|
r5566 | class wsgirequest(object): | ||
def __init__(self, wsgienv, start_response): | ||||
Eric Hopper
|
r2506 | version = wsgienv['wsgi.version'] | ||
Thomas Arendsen Hein
|
r3673 | if (version < (1, 0)) or (version >= (2, 0)): | ||
Thomas Arendsen Hein
|
r4633 | raise RuntimeError("Unknown and unsupported WSGI version %d.%d" | ||
Eric Hopper
|
r2506 | % version) | ||
self.inp = wsgienv['wsgi.input'] | ||||
self.err = wsgienv['wsgi.errors'] | ||||
self.threaded = wsgienv['wsgi.multithread'] | ||||
self.multiprocess = wsgienv['wsgi.multiprocess'] | ||||
self.run_once = wsgienv['wsgi.run_once'] | ||||
self.env = wsgienv | ||||
Nicolas Dumazet
|
r10261 | self.form = normalize(cgi.parse(self.inp, | ||
self.env, | ||||
keep_blank_values=1)) | ||||
Dirkjan Ochtman
|
r5888 | self._start_response = start_response | ||
Dirkjan Ochtman
|
r5993 | self.server_write = None | ||
Eric Hopper
|
r2506 | self.headers = [] | ||
def __iter__(self): | ||||
return iter([]) | ||||
Eric Hopper
|
r2355 | |||
Vadim Gelfer
|
r2464 | def read(self, count=-1): | ||
return self.inp.read(count) | ||||
Dirkjan Ochtman
|
r7180 | def drain(self): | ||
'''need to read all data from request, httplib is half-duplex''' | ||||
length = int(self.env.get('CONTENT_LENGTH', 0)) | ||||
for s in util.filechunkiter(self.inp, limit=length): | ||||
pass | ||||
Dirkjan Ochtman
|
r5993 | def respond(self, status, type=None, filename=None, length=0): | ||
Dirkjan Ochtman
|
r5888 | if self._start_response is not None: | ||
Dirkjan Ochtman
|
r5993 | |||
self.httphdr(type, filename, length) | ||||
Dirkjan Ochtman
|
r5888 | if not self.headers: | ||
Dirkjan Ochtman
|
r5922 | raise RuntimeError("request.write called before headers sent") | ||
Dirkjan Ochtman
|
r5888 | |||
Dirkjan Ochtman
|
r5926 | for k, v in self.headers: | ||
if not isinstance(v, str): | ||||
raise TypeError('header value must be string: %r' % v) | ||||
Dirkjan Ochtman
|
r5888 | if isinstance(status, ErrorResponse): | ||
Dirkjan Ochtman
|
r7742 | self.header(status.headers) | ||
Augie Fackler
|
r12739 | if status.code == HTTP_NOT_MODIFIED: | ||
# RFC 2616 Section 10.3.5: 304 Not Modified has cases where | ||||
# it MUST NOT include any headers other than these and no | ||||
# body | ||||
self.headers = [(k, v) for (k, v) in self.headers if | ||||
k in ('Date', 'ETag', 'Expires', | ||||
'Cache-Control', 'Vary')] | ||||
Sune Foldager
|
r9694 | status = statusmessage(status.code, status.message) | ||
Dirkjan Ochtman
|
r5993 | elif status == 200: | ||
status = '200 Script output follows' | ||||
Dirkjan Ochtman
|
r5888 | elif isinstance(status, int): | ||
status = statusmessage(status) | ||||
self.server_write = self._start_response(status, self.headers) | ||||
self._start_response = None | ||||
self.headers = [] | ||||
Dirkjan Ochtman
|
r5993 | def write(self, thing): | ||
if hasattr(thing, "__iter__"): | ||||
for part in thing: | ||||
self.write(part) | ||||
else: | ||||
thing = str(thing) | ||||
try: | ||||
self.server_write(thing) | ||||
except socket.error, inst: | ||||
if inst[0] != errno.ECONNRESET: | ||||
raise | ||||
Eric Hopper
|
r2355 | |||
Alexis S. L. Carvalho
|
r4246 | def writelines(self, lines): | ||
for line in lines: | ||||
self.write(line) | ||||
def flush(self): | ||||
return None | ||||
def close(self): | ||||
return None | ||||
Dirkjan Ochtman
|
r5930 | def header(self, headers=[('Content-Type','text/html')]): | ||
Eric Hopper
|
r2506 | self.headers.extend(headers) | ||
Eric Hopper
|
r2355 | |||
Dirkjan Ochtman
|
r5993 | def httphdr(self, type=None, filename=None, length=0, headers={}): | ||
Vadim Gelfer
|
r2466 | headers = headers.items() | ||
Dirkjan Ochtman
|
r5993 | if type is not None: | ||
headers.append(('Content-Type', type)) | ||||
Vadim Gelfer
|
r2434 | if filename: | ||
Thomas Arendsen Hein
|
r6137 | filename = (filename.split('/')[-1] | ||
.replace('\\', '\\\\').replace('"', '\\"')) | ||||
Thomas Arendsen Hein
|
r6136 | headers.append(('Content-Disposition', | ||
Thomas Arendsen Hein
|
r6137 | 'inline; filename="%s"' % filename)) | ||
Vadim Gelfer
|
r2434 | if length: | ||
Dirkjan Ochtman
|
r5930 | headers.append(('Content-Length', str(length))) | ||
Eric Hopper
|
r2355 | self.header(headers) | ||
Dirkjan Ochtman
|
r5566 | |||
def wsgiapplication(app_maker): | ||||
Dirkjan Ochtman
|
r5887 | '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir() | ||
can and should now be used as a WSGI application.''' | ||||
Thomas Arendsen Hein
|
r5760 | application = app_maker() | ||
def run_wsgi(env, respond): | ||||
Dirkjan Ochtman
|
r5887 | return application(env, respond) | ||
Thomas Arendsen Hein
|
r5760 | return run_wsgi | ||