##// END OF EJS Templates
hgweb: when constructing or adding to a wsgi environ dict, use native strs...
Augie Fackler -
r34513:482d6f6d default
parent child Browse files
Show More
@@ -1,152 +1,152 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import cgi
11 import cgi
12 import errno
12 import errno
13 import socket
13 import socket
14
14
15 from .common import (
15 from .common import (
16 ErrorResponse,
16 ErrorResponse,
17 HTTP_NOT_MODIFIED,
17 HTTP_NOT_MODIFIED,
18 statusmessage,
18 statusmessage,
19 )
19 )
20
20
21 from .. import (
21 from .. import (
22 util,
22 util,
23 )
23 )
24
24
25 shortcuts = {
25 shortcuts = {
26 'cl': [('cmd', ['changelog']), ('rev', None)],
26 'cl': [('cmd', ['changelog']), ('rev', None)],
27 'sl': [('cmd', ['shortlog']), ('rev', None)],
27 'sl': [('cmd', ['shortlog']), ('rev', None)],
28 'cs': [('cmd', ['changeset']), ('node', None)],
28 'cs': [('cmd', ['changeset']), ('node', None)],
29 'f': [('cmd', ['file']), ('filenode', None)],
29 'f': [('cmd', ['file']), ('filenode', None)],
30 'fl': [('cmd', ['filelog']), ('filenode', None)],
30 'fl': [('cmd', ['filelog']), ('filenode', None)],
31 'fd': [('cmd', ['filediff']), ('node', None)],
31 'fd': [('cmd', ['filediff']), ('node', None)],
32 'fa': [('cmd', ['annotate']), ('filenode', None)],
32 'fa': [('cmd', ['annotate']), ('filenode', None)],
33 'mf': [('cmd', ['manifest']), ('manifest', None)],
33 'mf': [('cmd', ['manifest']), ('manifest', None)],
34 'ca': [('cmd', ['archive']), ('node', None)],
34 'ca': [('cmd', ['archive']), ('node', None)],
35 'tags': [('cmd', ['tags'])],
35 'tags': [('cmd', ['tags'])],
36 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
36 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
37 'static': [('cmd', ['static']), ('file', None)]
37 'static': [('cmd', ['static']), ('file', None)]
38 }
38 }
39
39
40 def normalize(form):
40 def normalize(form):
41 # first expand the shortcuts
41 # first expand the shortcuts
42 for k in shortcuts.iterkeys():
42 for k in shortcuts.iterkeys():
43 if k in form:
43 if k in form:
44 for name, value in shortcuts[k]:
44 for name, value in shortcuts[k]:
45 if value is None:
45 if value is None:
46 value = form[k]
46 value = form[k]
47 form[name] = value
47 form[name] = value
48 del form[k]
48 del form[k]
49 # And strip the values
49 # And strip the values
50 for k, v in form.iteritems():
50 for k, v in form.iteritems():
51 form[k] = [i.strip() for i in v]
51 form[k] = [i.strip() for i in v]
52 return form
52 return form
53
53
54 class wsgirequest(object):
54 class wsgirequest(object):
55 """Higher-level API for a WSGI request.
55 """Higher-level API for a WSGI request.
56
56
57 WSGI applications are invoked with 2 arguments. They are used to
57 WSGI applications are invoked with 2 arguments. They are used to
58 instantiate instances of this class, which provides higher-level APIs
58 instantiate instances of this class, which provides higher-level APIs
59 for obtaining request parameters, writing HTTP output, etc.
59 for obtaining request parameters, writing HTTP output, etc.
60 """
60 """
61 def __init__(self, wsgienv, start_response):
61 def __init__(self, wsgienv, start_response):
62 version = wsgienv['wsgi.version']
62 version = wsgienv[r'wsgi.version']
63 if (version < (1, 0)) or (version >= (2, 0)):
63 if (version < (1, 0)) or (version >= (2, 0)):
64 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
64 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
65 % version)
65 % version)
66 self.inp = wsgienv['wsgi.input']
66 self.inp = wsgienv[r'wsgi.input']
67 self.err = wsgienv['wsgi.errors']
67 self.err = wsgienv[r'wsgi.errors']
68 self.threaded = wsgienv['wsgi.multithread']
68 self.threaded = wsgienv[r'wsgi.multithread']
69 self.multiprocess = wsgienv['wsgi.multiprocess']
69 self.multiprocess = wsgienv[r'wsgi.multiprocess']
70 self.run_once = wsgienv['wsgi.run_once']
70 self.run_once = wsgienv[r'wsgi.run_once']
71 self.env = wsgienv
71 self.env = wsgienv
72 self.form = normalize(cgi.parse(self.inp,
72 self.form = normalize(cgi.parse(self.inp,
73 self.env,
73 self.env,
74 keep_blank_values=1))
74 keep_blank_values=1))
75 self._start_response = start_response
75 self._start_response = start_response
76 self.server_write = None
76 self.server_write = None
77 self.headers = []
77 self.headers = []
78
78
79 def __iter__(self):
79 def __iter__(self):
80 return iter([])
80 return iter([])
81
81
82 def read(self, count=-1):
82 def read(self, count=-1):
83 return self.inp.read(count)
83 return self.inp.read(count)
84
84
85 def drain(self):
85 def drain(self):
86 '''need to read all data from request, httplib is half-duplex'''
86 '''need to read all data from request, httplib is half-duplex'''
87 length = int(self.env.get('CONTENT_LENGTH') or 0)
87 length = int(self.env.get('CONTENT_LENGTH') or 0)
88 for s in util.filechunkiter(self.inp, limit=length):
88 for s in util.filechunkiter(self.inp, limit=length):
89 pass
89 pass
90
90
91 def respond(self, status, type, filename=None, body=None):
91 def respond(self, status, type, filename=None, body=None):
92 if self._start_response is not None:
92 if self._start_response is not None:
93 self.headers.append(('Content-Type', type))
93 self.headers.append(('Content-Type', type))
94 if filename:
94 if filename:
95 filename = (filename.rpartition('/')[-1]
95 filename = (filename.rpartition('/')[-1]
96 .replace('\\', '\\\\').replace('"', '\\"'))
96 .replace('\\', '\\\\').replace('"', '\\"'))
97 self.headers.append(('Content-Disposition',
97 self.headers.append(('Content-Disposition',
98 'inline; filename="%s"' % filename))
98 'inline; filename="%s"' % filename))
99 if body is not None:
99 if body is not None:
100 self.headers.append(('Content-Length', str(len(body))))
100 self.headers.append(('Content-Length', str(len(body))))
101
101
102 for k, v in self.headers:
102 for k, v in self.headers:
103 if not isinstance(v, str):
103 if not isinstance(v, str):
104 raise TypeError('header value must be string: %r' % (v,))
104 raise TypeError('header value must be string: %r' % (v,))
105
105
106 if isinstance(status, ErrorResponse):
106 if isinstance(status, ErrorResponse):
107 self.headers.extend(status.headers)
107 self.headers.extend(status.headers)
108 if status.code == HTTP_NOT_MODIFIED:
108 if status.code == HTTP_NOT_MODIFIED:
109 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
109 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
110 # it MUST NOT include any headers other than these and no
110 # it MUST NOT include any headers other than these and no
111 # body
111 # body
112 self.headers = [(k, v) for (k, v) in self.headers if
112 self.headers = [(k, v) for (k, v) in self.headers if
113 k in ('Date', 'ETag', 'Expires',
113 k in ('Date', 'ETag', 'Expires',
114 'Cache-Control', 'Vary')]
114 'Cache-Control', 'Vary')]
115 status = statusmessage(status.code, str(status))
115 status = statusmessage(status.code, str(status))
116 elif status == 200:
116 elif status == 200:
117 status = '200 Script output follows'
117 status = '200 Script output follows'
118 elif isinstance(status, int):
118 elif isinstance(status, int):
119 status = statusmessage(status)
119 status = statusmessage(status)
120
120
121 self.server_write = self._start_response(status, self.headers)
121 self.server_write = self._start_response(status, self.headers)
122 self._start_response = None
122 self._start_response = None
123 self.headers = []
123 self.headers = []
124 if body is not None:
124 if body is not None:
125 self.write(body)
125 self.write(body)
126 self.server_write = None
126 self.server_write = None
127
127
128 def write(self, thing):
128 def write(self, thing):
129 if thing:
129 if thing:
130 try:
130 try:
131 self.server_write(thing)
131 self.server_write(thing)
132 except socket.error as inst:
132 except socket.error as inst:
133 if inst[0] != errno.ECONNRESET:
133 if inst[0] != errno.ECONNRESET:
134 raise
134 raise
135
135
136 def writelines(self, lines):
136 def writelines(self, lines):
137 for line in lines:
137 for line in lines:
138 self.write(line)
138 self.write(line)
139
139
140 def flush(self):
140 def flush(self):
141 return None
141 return None
142
142
143 def close(self):
143 def close(self):
144 return None
144 return None
145
145
146 def wsgiapplication(app_maker):
146 def wsgiapplication(app_maker):
147 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
147 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
148 can and should now be used as a WSGI application.'''
148 can and should now be used as a WSGI application.'''
149 application = app_maker()
149 application = app_maker()
150 def run_wsgi(env, respond):
150 def run_wsgi(env, respond):
151 return application(env, respond)
151 return application(env, respond)
152 return run_wsgi
152 return run_wsgi
@@ -1,334 +1,334 b''
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import socket
13 import socket
14 import sys
14 import sys
15 import traceback
15 import traceback
16
16
17 from ..i18n import _
17 from ..i18n import _
18
18
19 from .. import (
19 from .. import (
20 error,
20 error,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25 httpservermod = util.httpserver
25 httpservermod = util.httpserver
26 socketserver = util.socketserver
26 socketserver = util.socketserver
27 urlerr = util.urlerr
27 urlerr = util.urlerr
28 urlreq = util.urlreq
28 urlreq = util.urlreq
29
29
30 from . import (
30 from . import (
31 common,
31 common,
32 )
32 )
33
33
34 def _splitURI(uri):
34 def _splitURI(uri):
35 """Return path and query that has been split from uri
35 """Return path and query that has been split from uri
36
36
37 Just like CGI environment, the path is unquoted, the query is
37 Just like CGI environment, the path is unquoted, the query is
38 not.
38 not.
39 """
39 """
40 if '?' in uri:
40 if '?' in uri:
41 path, query = uri.split('?', 1)
41 path, query = uri.split('?', 1)
42 else:
42 else:
43 path, query = uri, ''
43 path, query = uri, ''
44 return urlreq.unquote(path), query
44 return urlreq.unquote(path), query
45
45
46 class _error_logger(object):
46 class _error_logger(object):
47 def __init__(self, handler):
47 def __init__(self, handler):
48 self.handler = handler
48 self.handler = handler
49 def flush(self):
49 def flush(self):
50 pass
50 pass
51 def write(self, str):
51 def write(self, str):
52 self.writelines(str.split('\n'))
52 self.writelines(str.split('\n'))
53 def writelines(self, seq):
53 def writelines(self, seq):
54 for msg in seq:
54 for msg in seq:
55 self.handler.log_error("HG error: %s", msg)
55 self.handler.log_error("HG error: %s", msg)
56
56
57 class _httprequesthandler(httpservermod.basehttprequesthandler):
57 class _httprequesthandler(httpservermod.basehttprequesthandler):
58
58
59 url_scheme = 'http'
59 url_scheme = 'http'
60
60
61 @staticmethod
61 @staticmethod
62 def preparehttpserver(httpserver, ui):
62 def preparehttpserver(httpserver, ui):
63 """Prepare .socket of new HTTPServer instance"""
63 """Prepare .socket of new HTTPServer instance"""
64
64
65 def __init__(self, *args, **kargs):
65 def __init__(self, *args, **kargs):
66 self.protocol_version = 'HTTP/1.1'
66 self.protocol_version = r'HTTP/1.1'
67 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
67 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
68
68
69 def _log_any(self, fp, format, *args):
69 def _log_any(self, fp, format, *args):
70 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
70 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
71 self.log_date_time_string(),
71 self.log_date_time_string(),
72 format % args))
72 format % args))
73 fp.flush()
73 fp.flush()
74
74
75 def log_error(self, format, *args):
75 def log_error(self, format, *args):
76 self._log_any(self.server.errorlog, format, *args)
76 self._log_any(self.server.errorlog, format, *args)
77
77
78 def log_message(self, format, *args):
78 def log_message(self, format, *args):
79 self._log_any(self.server.accesslog, format, *args)
79 self._log_any(self.server.accesslog, format, *args)
80
80
81 def log_request(self, code='-', size='-'):
81 def log_request(self, code='-', size='-'):
82 xheaders = []
82 xheaders = []
83 if util.safehasattr(self, 'headers'):
83 if util.safehasattr(self, 'headers'):
84 xheaders = [h for h in self.headers.items()
84 xheaders = [h for h in self.headers.items()
85 if h[0].startswith('x-')]
85 if h[0].startswith('x-')]
86 self.log_message('"%s" %s %s%s',
86 self.log_message('"%s" %s %s%s',
87 self.requestline, str(code), str(size),
87 self.requestline, str(code), str(size),
88 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
88 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
89
89
90 def do_write(self):
90 def do_write(self):
91 try:
91 try:
92 self.do_hgweb()
92 self.do_hgweb()
93 except socket.error as inst:
93 except socket.error as inst:
94 if inst[0] != errno.EPIPE:
94 if inst[0] != errno.EPIPE:
95 raise
95 raise
96
96
97 def do_POST(self):
97 def do_POST(self):
98 try:
98 try:
99 self.do_write()
99 self.do_write()
100 except Exception:
100 except Exception:
101 self._start_response("500 Internal Server Error", [])
101 self._start_response("500 Internal Server Error", [])
102 self._write("Internal Server Error")
102 self._write("Internal Server Error")
103 self._done()
103 self._done()
104 tb = "".join(traceback.format_exception(*sys.exc_info()))
104 tb = "".join(traceback.format_exception(*sys.exc_info()))
105 self.log_error("Exception happened during processing "
105 self.log_error("Exception happened during processing "
106 "request '%s':\n%s", self.path, tb)
106 "request '%s':\n%s", self.path, tb)
107
107
108 def do_GET(self):
108 def do_GET(self):
109 self.do_POST()
109 self.do_POST()
110
110
111 def do_hgweb(self):
111 def do_hgweb(self):
112 path, query = _splitURI(self.path)
112 path, query = _splitURI(self.path)
113
113
114 env = {}
114 env = {}
115 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
115 env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
116 env['REQUEST_METHOD'] = self.command
116 env[r'REQUEST_METHOD'] = self.command
117 env['SERVER_NAME'] = self.server.server_name
117 env[r'SERVER_NAME'] = self.server.server_name
118 env['SERVER_PORT'] = str(self.server.server_port)
118 env[r'SERVER_PORT'] = str(self.server.server_port)
119 env['REQUEST_URI'] = self.path
119 env[r'REQUEST_URI'] = self.path
120 env['SCRIPT_NAME'] = self.server.prefix
120 env[r'SCRIPT_NAME'] = self.server.prefix
121 env['PATH_INFO'] = path[len(self.server.prefix):]
121 env[r'PATH_INFO'] = path[len(self.server.prefix):]
122 env['REMOTE_HOST'] = self.client_address[0]
122 env[r'REMOTE_HOST'] = self.client_address[0]
123 env['REMOTE_ADDR'] = self.client_address[0]
123 env[r'REMOTE_ADDR'] = self.client_address[0]
124 if query:
124 if query:
125 env['QUERY_STRING'] = query
125 env[r'QUERY_STRING'] = query
126
126
127 if self.headers.typeheader is None:
127 if self.headers.typeheader is None:
128 env['CONTENT_TYPE'] = self.headers.type
128 env[r'CONTENT_TYPE'] = self.headers.type
129 else:
129 else:
130 env['CONTENT_TYPE'] = self.headers.typeheader
130 env[r'CONTENT_TYPE'] = self.headers.typeheader
131 length = self.headers.getheader('content-length')
131 length = self.headers.getheader('content-length')
132 if length:
132 if length:
133 env['CONTENT_LENGTH'] = length
133 env[r'CONTENT_LENGTH'] = length
134 for header in [h for h in self.headers.keys()
134 for header in [h for h in self.headers.keys()
135 if h not in ('content-type', 'content-length')]:
135 if h not in ('content-type', 'content-length')]:
136 hkey = 'HTTP_' + header.replace('-', '_').upper()
136 hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
137 hval = self.headers.getheader(header)
137 hval = self.headers.get(header)
138 hval = hval.replace('\n', '').strip()
138 hval = hval.replace(r'\n', r'').strip()
139 if hval:
139 if hval:
140 env[hkey] = hval
140 env[hkey] = hval
141 env['SERVER_PROTOCOL'] = self.request_version
141 env[r'SERVER_PROTOCOL'] = self.request_version
142 env['wsgi.version'] = (1, 0)
142 env[r'wsgi.version'] = (1, 0)
143 env['wsgi.url_scheme'] = self.url_scheme
143 env[r'wsgi.url_scheme'] = self.url_scheme
144 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
144 if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
145 self.rfile = common.continuereader(self.rfile, self.wfile.write)
145 self.rfile = common.continuereader(self.rfile, self.wfile.write)
146
146
147 env['wsgi.input'] = self.rfile
147 env[r'wsgi.input'] = self.rfile
148 env['wsgi.errors'] = _error_logger(self)
148 env[r'wsgi.errors'] = _error_logger(self)
149 env['wsgi.multithread'] = isinstance(self.server,
149 env[r'wsgi.multithread'] = isinstance(self.server,
150 socketserver.ThreadingMixIn)
150 socketserver.ThreadingMixIn)
151 env['wsgi.multiprocess'] = isinstance(self.server,
151 env[r'wsgi.multiprocess'] = isinstance(self.server,
152 socketserver.ForkingMixIn)
152 socketserver.ForkingMixIn)
153 env['wsgi.run_once'] = 0
153 env[r'wsgi.run_once'] = 0
154
154
155 self.saved_status = None
155 self.saved_status = None
156 self.saved_headers = []
156 self.saved_headers = []
157 self.sent_headers = False
157 self.sent_headers = False
158 self.length = None
158 self.length = None
159 self._chunked = None
159 self._chunked = None
160 for chunk in self.server.application(env, self._start_response):
160 for chunk in self.server.application(env, self._start_response):
161 self._write(chunk)
161 self._write(chunk)
162 if not self.sent_headers:
162 if not self.sent_headers:
163 self.send_headers()
163 self.send_headers()
164 self._done()
164 self._done()
165
165
166 def send_headers(self):
166 def send_headers(self):
167 if not self.saved_status:
167 if not self.saved_status:
168 raise AssertionError("Sending headers before "
168 raise AssertionError("Sending headers before "
169 "start_response() called")
169 "start_response() called")
170 saved_status = self.saved_status.split(None, 1)
170 saved_status = self.saved_status.split(None, 1)
171 saved_status[0] = int(saved_status[0])
171 saved_status[0] = int(saved_status[0])
172 self.send_response(*saved_status)
172 self.send_response(*saved_status)
173 self.length = None
173 self.length = None
174 self._chunked = False
174 self._chunked = False
175 for h in self.saved_headers:
175 for h in self.saved_headers:
176 self.send_header(*h)
176 self.send_header(*h)
177 if h[0].lower() == 'content-length':
177 if h[0].lower() == 'content-length':
178 self.length = int(h[1])
178 self.length = int(h[1])
179 if (self.length is None and
179 if (self.length is None and
180 saved_status[0] != common.HTTP_NOT_MODIFIED):
180 saved_status[0] != common.HTTP_NOT_MODIFIED):
181 self._chunked = (not self.close_connection and
181 self._chunked = (not self.close_connection and
182 self.request_version == "HTTP/1.1")
182 self.request_version == "HTTP/1.1")
183 if self._chunked:
183 if self._chunked:
184 self.send_header('Transfer-Encoding', 'chunked')
184 self.send_header('Transfer-Encoding', 'chunked')
185 else:
185 else:
186 self.send_header('Connection', 'close')
186 self.send_header('Connection', 'close')
187 self.end_headers()
187 self.end_headers()
188 self.sent_headers = True
188 self.sent_headers = True
189
189
190 def _start_response(self, http_status, headers, exc_info=None):
190 def _start_response(self, http_status, headers, exc_info=None):
191 code, msg = http_status.split(None, 1)
191 code, msg = http_status.split(None, 1)
192 code = int(code)
192 code = int(code)
193 self.saved_status = http_status
193 self.saved_status = http_status
194 bad_headers = ('connection', 'transfer-encoding')
194 bad_headers = ('connection', 'transfer-encoding')
195 self.saved_headers = [h for h in headers
195 self.saved_headers = [h for h in headers
196 if h[0].lower() not in bad_headers]
196 if h[0].lower() not in bad_headers]
197 return self._write
197 return self._write
198
198
199 def _write(self, data):
199 def _write(self, data):
200 if not self.saved_status:
200 if not self.saved_status:
201 raise AssertionError("data written before start_response() called")
201 raise AssertionError("data written before start_response() called")
202 elif not self.sent_headers:
202 elif not self.sent_headers:
203 self.send_headers()
203 self.send_headers()
204 if self.length is not None:
204 if self.length is not None:
205 if len(data) > self.length:
205 if len(data) > self.length:
206 raise AssertionError("Content-length header sent, but more "
206 raise AssertionError("Content-length header sent, but more "
207 "bytes than specified are being written.")
207 "bytes than specified are being written.")
208 self.length = self.length - len(data)
208 self.length = self.length - len(data)
209 elif self._chunked and data:
209 elif self._chunked and data:
210 data = '%x\r\n%s\r\n' % (len(data), data)
210 data = '%x\r\n%s\r\n' % (len(data), data)
211 self.wfile.write(data)
211 self.wfile.write(data)
212 self.wfile.flush()
212 self.wfile.flush()
213
213
214 def _done(self):
214 def _done(self):
215 if self._chunked:
215 if self._chunked:
216 self.wfile.write('0\r\n\r\n')
216 self.wfile.write('0\r\n\r\n')
217 self.wfile.flush()
217 self.wfile.flush()
218
218
219 class _httprequesthandlerssl(_httprequesthandler):
219 class _httprequesthandlerssl(_httprequesthandler):
220 """HTTPS handler based on Python's ssl module"""
220 """HTTPS handler based on Python's ssl module"""
221
221
222 url_scheme = 'https'
222 url_scheme = 'https'
223
223
224 @staticmethod
224 @staticmethod
225 def preparehttpserver(httpserver, ui):
225 def preparehttpserver(httpserver, ui):
226 try:
226 try:
227 from .. import sslutil
227 from .. import sslutil
228 sslutil.modernssl
228 sslutil.modernssl
229 except ImportError:
229 except ImportError:
230 raise error.Abort(_("SSL support is unavailable"))
230 raise error.Abort(_("SSL support is unavailable"))
231
231
232 certfile = ui.config('web', 'certificate')
232 certfile = ui.config('web', 'certificate')
233
233
234 # These config options are currently only meant for testing. Use
234 # These config options are currently only meant for testing. Use
235 # at your own risk.
235 # at your own risk.
236 cafile = ui.config('devel', 'servercafile')
236 cafile = ui.config('devel', 'servercafile')
237 reqcert = ui.configbool('devel', 'serverrequirecert')
237 reqcert = ui.configbool('devel', 'serverrequirecert')
238
238
239 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
239 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
240 ui,
240 ui,
241 certfile=certfile,
241 certfile=certfile,
242 cafile=cafile,
242 cafile=cafile,
243 requireclientcert=reqcert)
243 requireclientcert=reqcert)
244
244
245 def setup(self):
245 def setup(self):
246 self.connection = self.request
246 self.connection = self.request
247 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
247 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
248 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
248 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
249
249
250 try:
250 try:
251 import threading
251 import threading
252 threading.activeCount() # silence pyflakes and bypass demandimport
252 threading.activeCount() # silence pyflakes and bypass demandimport
253 _mixin = socketserver.ThreadingMixIn
253 _mixin = socketserver.ThreadingMixIn
254 except ImportError:
254 except ImportError:
255 if util.safehasattr(os, "fork"):
255 if util.safehasattr(os, "fork"):
256 _mixin = socketserver.ForkingMixIn
256 _mixin = socketserver.ForkingMixIn
257 else:
257 else:
258 class _mixin(object):
258 class _mixin(object):
259 pass
259 pass
260
260
261 def openlog(opt, default):
261 def openlog(opt, default):
262 if opt and opt != '-':
262 if opt and opt != '-':
263 return open(opt, 'a')
263 return open(opt, 'a')
264 return default
264 return default
265
265
266 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
266 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
267
267
268 # SO_REUSEADDR has broken semantics on windows
268 # SO_REUSEADDR has broken semantics on windows
269 if pycompat.osname == 'nt':
269 if pycompat.osname == 'nt':
270 allow_reuse_address = 0
270 allow_reuse_address = 0
271
271
272 def __init__(self, ui, app, addr, handler, **kwargs):
272 def __init__(self, ui, app, addr, handler, **kwargs):
273 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
273 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
274 self.daemon_threads = True
274 self.daemon_threads = True
275 self.application = app
275 self.application = app
276
276
277 handler.preparehttpserver(self, ui)
277 handler.preparehttpserver(self, ui)
278
278
279 prefix = ui.config('web', 'prefix')
279 prefix = ui.config('web', 'prefix')
280 if prefix:
280 if prefix:
281 prefix = '/' + prefix.strip('/')
281 prefix = '/' + prefix.strip('/')
282 self.prefix = prefix
282 self.prefix = prefix
283
283
284 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
284 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
285 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
285 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
286 self.accesslog = alog
286 self.accesslog = alog
287 self.errorlog = elog
287 self.errorlog = elog
288
288
289 self.addr, self.port = self.socket.getsockname()[0:2]
289 self.addr, self.port = self.socket.getsockname()[0:2]
290 self.fqaddr = socket.getfqdn(addr[0])
290 self.fqaddr = socket.getfqdn(addr[0])
291
291
292 class IPv6HTTPServer(MercurialHTTPServer):
292 class IPv6HTTPServer(MercurialHTTPServer):
293 address_family = getattr(socket, 'AF_INET6', None)
293 address_family = getattr(socket, 'AF_INET6', None)
294 def __init__(self, *args, **kwargs):
294 def __init__(self, *args, **kwargs):
295 if self.address_family is None:
295 if self.address_family is None:
296 raise error.RepoError(_('IPv6 is not available on this system'))
296 raise error.RepoError(_('IPv6 is not available on this system'))
297 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
297 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
298
298
299 def create_server(ui, app):
299 def create_server(ui, app):
300
300
301 if ui.config('web', 'certificate'):
301 if ui.config('web', 'certificate'):
302 handler = _httprequesthandlerssl
302 handler = _httprequesthandlerssl
303 else:
303 else:
304 handler = _httprequesthandler
304 handler = _httprequesthandler
305
305
306 if ui.configbool('web', 'ipv6'):
306 if ui.configbool('web', 'ipv6'):
307 cls = IPv6HTTPServer
307 cls = IPv6HTTPServer
308 else:
308 else:
309 cls = MercurialHTTPServer
309 cls = MercurialHTTPServer
310
310
311 # ugly hack due to python issue5853 (for threaded use)
311 # ugly hack due to python issue5853 (for threaded use)
312 try:
312 try:
313 import mimetypes
313 import mimetypes
314 mimetypes.init()
314 mimetypes.init()
315 except UnicodeDecodeError:
315 except UnicodeDecodeError:
316 # Python 2.x's mimetypes module attempts to decode strings
316 # Python 2.x's mimetypes module attempts to decode strings
317 # from Windows' ANSI APIs as ascii (fail), then re-encode them
317 # from Windows' ANSI APIs as ascii (fail), then re-encode them
318 # as ascii (clown fail), because the default Python Unicode
318 # as ascii (clown fail), because the default Python Unicode
319 # codec is hardcoded as ascii.
319 # codec is hardcoded as ascii.
320
320
321 sys.argv # unwrap demand-loader so that reload() works
321 sys.argv # unwrap demand-loader so that reload() works
322 reload(sys) # resurrect sys.setdefaultencoding()
322 reload(sys) # resurrect sys.setdefaultencoding()
323 oldenc = sys.getdefaultencoding()
323 oldenc = sys.getdefaultencoding()
324 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
324 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
325 mimetypes.init()
325 mimetypes.init()
326 sys.setdefaultencoding(oldenc)
326 sys.setdefaultencoding(oldenc)
327
327
328 address = ui.config('web', 'address')
328 address = ui.config('web', 'address')
329 port = util.getport(ui.config('web', 'port'))
329 port = util.getport(ui.config('web', 'port'))
330 try:
330 try:
331 return cls(ui, app, (address, port), handler)
331 return cls(ui, app, (address, port), handler)
332 except socket.error as inst:
332 except socket.error as inst:
333 raise error.Abort(_("cannot start server at '%s:%d': %s")
333 raise error.Abort(_("cannot start server at '%s:%d': %s")
334 % (address, port, inst.args[1]))
334 % (address, port, inst.args[1]))
@@ -1,90 +1,90 b''
1 # hgweb/wsgicgi.py - CGI->WSGI translator
1 # hgweb/wsgicgi.py - CGI->WSGI translator
2 #
2 #
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # This was originally copied from the public domain code at
8 # This was originally copied from the public domain code at
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 from .. import (
13 from .. import (
14 encoding,
14 encoding,
15 util,
15 util,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 common,
19 common,
20 )
20 )
21
21
22 def launch(application):
22 def launch(application):
23 util.setbinary(util.stdin)
23 util.setbinary(util.stdin)
24 util.setbinary(util.stdout)
24 util.setbinary(util.stdout)
25
25
26 environ = dict(encoding.environ.iteritems())
26 environ = dict(encoding.environ.iteritems())
27 environ.setdefault('PATH_INFO', '')
27 environ.setdefault(r'PATH_INFO', '')
28 if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
28 if environ.get(r'SERVER_SOFTWARE', r'').startswith(r'Microsoft-IIS'):
29 # IIS includes script_name in PATH_INFO
29 # IIS includes script_name in PATH_INFO
30 scriptname = environ['SCRIPT_NAME']
30 scriptname = environ[r'SCRIPT_NAME']
31 if environ['PATH_INFO'].startswith(scriptname):
31 if environ[r'PATH_INFO'].startswith(scriptname):
32 environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
32 environ[r'PATH_INFO'] = environ[r'PATH_INFO'][len(scriptname):]
33
33
34 stdin = util.stdin
34 stdin = util.stdin
35 if environ.get('HTTP_EXPECT', '').lower() == '100-continue':
35 if environ.get(r'HTTP_EXPECT', r'').lower() == r'100-continue':
36 stdin = common.continuereader(stdin, util.stdout.write)
36 stdin = common.continuereader(stdin, util.stdout.write)
37
37
38 environ['wsgi.input'] = stdin
38 environ[r'wsgi.input'] = stdin
39 environ['wsgi.errors'] = util.stderr
39 environ[r'wsgi.errors'] = util.stderr
40 environ['wsgi.version'] = (1, 0)
40 environ[r'wsgi.version'] = (1, 0)
41 environ['wsgi.multithread'] = False
41 environ[r'wsgi.multithread'] = False
42 environ['wsgi.multiprocess'] = True
42 environ[r'wsgi.multiprocess'] = True
43 environ['wsgi.run_once'] = True
43 environ[r'wsgi.run_once'] = True
44
44
45 if environ.get('HTTPS', 'off').lower() in ('on', '1', 'yes'):
45 if environ.get(r'HTTPS', r'off').lower() in (r'on', r'1', r'yes'):
46 environ['wsgi.url_scheme'] = 'https'
46 environ[r'wsgi.url_scheme'] = r'https'
47 else:
47 else:
48 environ['wsgi.url_scheme'] = 'http'
48 environ[r'wsgi.url_scheme'] = r'http'
49
49
50 headers_set = []
50 headers_set = []
51 headers_sent = []
51 headers_sent = []
52 out = util.stdout
52 out = util.stdout
53
53
54 def write(data):
54 def write(data):
55 if not headers_set:
55 if not headers_set:
56 raise AssertionError("write() before start_response()")
56 raise AssertionError("write() before start_response()")
57
57
58 elif not headers_sent:
58 elif not headers_sent:
59 # Before the first output, send the stored headers
59 # Before the first output, send the stored headers
60 status, response_headers = headers_sent[:] = headers_set
60 status, response_headers = headers_sent[:] = headers_set
61 out.write('Status: %s\r\n' % status)
61 out.write('Status: %s\r\n' % status)
62 for header in response_headers:
62 for header in response_headers:
63 out.write('%s: %s\r\n' % header)
63 out.write('%s: %s\r\n' % header)
64 out.write('\r\n')
64 out.write('\r\n')
65
65
66 out.write(data)
66 out.write(data)
67 out.flush()
67 out.flush()
68
68
69 def start_response(status, response_headers, exc_info=None):
69 def start_response(status, response_headers, exc_info=None):
70 if exc_info:
70 if exc_info:
71 try:
71 try:
72 if headers_sent:
72 if headers_sent:
73 # Re-raise original exception if headers sent
73 # Re-raise original exception if headers sent
74 raise exc_info[0](exc_info[1], exc_info[2])
74 raise exc_info[0](exc_info[1], exc_info[2])
75 finally:
75 finally:
76 exc_info = None # avoid dangling circular ref
76 exc_info = None # avoid dangling circular ref
77 elif headers_set:
77 elif headers_set:
78 raise AssertionError("Headers already set!")
78 raise AssertionError("Headers already set!")
79
79
80 headers_set[:] = [status, response_headers]
80 headers_set[:] = [status, response_headers]
81 return write
81 return write
82
82
83 content = application(environ, start_response)
83 content = application(environ, start_response)
84 try:
84 try:
85 for chunk in content:
85 for chunk in content:
86 write(chunk)
86 write(chunk)
87 if not headers_sent:
87 if not headers_sent:
88 write('') # send headers now if body was empty
88 write('') # send headers now if body was empty
89 finally:
89 finally:
90 getattr(content, 'close', lambda: None)()
90 getattr(content, 'close', lambda: None)()
General Comments 0
You need to be logged in to leave comments. Login now