##// END OF EJS Templates
hgweb: pre-init mimetypes module (fixes ugly bug in python-2.6.2 mimetypes)...
Dirkjan Ochtman -
r8224:1075f5c1 default
parent child Browse files
Show More
@@ -1,293 +1,296 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
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 from mercurial import hg, util, error
10 from mercurial import hg, util, error
11 from hgweb_mod import hgweb
11 from hgweb_mod import hgweb
12 from hgwebdir_mod import hgwebdir
12 from hgwebdir_mod import hgwebdir
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14
14
15 def _splitURI(uri):
15 def _splitURI(uri):
16 """ Return path and query splited from uri
16 """ Return path and query splited from uri
17
17
18 Just like CGI environment, the path is unquoted, the query is
18 Just like CGI environment, the path is unquoted, the query is
19 not.
19 not.
20 """
20 """
21 if '?' in uri:
21 if '?' in uri:
22 path, query = uri.split('?', 1)
22 path, query = uri.split('?', 1)
23 else:
23 else:
24 path, query = uri, ''
24 path, query = uri, ''
25 return urllib.unquote(path), query
25 return urllib.unquote(path), query
26
26
27 class _error_logger(object):
27 class _error_logger(object):
28 def __init__(self, handler):
28 def __init__(self, handler):
29 self.handler = handler
29 self.handler = handler
30 def flush(self):
30 def flush(self):
31 pass
31 pass
32 def write(self, str):
32 def write(self, str):
33 self.writelines(str.split('\n'))
33 self.writelines(str.split('\n'))
34 def writelines(self, seq):
34 def writelines(self, seq):
35 for msg in seq:
35 for msg in seq:
36 self.handler.log_error("HG error: %s", msg)
36 self.handler.log_error("HG error: %s", msg)
37
37
38 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
38 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39
39
40 url_scheme = 'http'
40 url_scheme = 'http'
41
41
42 def __init__(self, *args, **kargs):
42 def __init__(self, *args, **kargs):
43 self.protocol_version = 'HTTP/1.1'
43 self.protocol_version = 'HTTP/1.1'
44 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
44 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
45
45
46 def _log_any(self, fp, format, *args):
46 def _log_any(self, fp, format, *args):
47 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
47 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
48 self.log_date_time_string(),
48 self.log_date_time_string(),
49 format % args))
49 format % args))
50 fp.flush()
50 fp.flush()
51
51
52 def log_error(self, format, *args):
52 def log_error(self, format, *args):
53 self._log_any(self.server.errorlog, format, *args)
53 self._log_any(self.server.errorlog, format, *args)
54
54
55 def log_message(self, format, *args):
55 def log_message(self, format, *args):
56 self._log_any(self.server.accesslog, format, *args)
56 self._log_any(self.server.accesslog, format, *args)
57
57
58 def do_write(self):
58 def do_write(self):
59 try:
59 try:
60 self.do_hgweb()
60 self.do_hgweb()
61 except socket.error, inst:
61 except socket.error, inst:
62 if inst[0] != errno.EPIPE:
62 if inst[0] != errno.EPIPE:
63 raise
63 raise
64
64
65 def do_POST(self):
65 def do_POST(self):
66 try:
66 try:
67 self.do_write()
67 self.do_write()
68 except StandardError:
68 except StandardError:
69 self._start_response("500 Internal Server Error", [])
69 self._start_response("500 Internal Server Error", [])
70 self._write("Internal Server Error")
70 self._write("Internal Server Error")
71 tb = "".join(traceback.format_exception(*sys.exc_info()))
71 tb = "".join(traceback.format_exception(*sys.exc_info()))
72 self.log_error("Exception happened during processing request '%s':\n%s",
72 self.log_error("Exception happened during processing request '%s':\n%s",
73 self.path, tb)
73 self.path, tb)
74
74
75 def do_GET(self):
75 def do_GET(self):
76 self.do_POST()
76 self.do_POST()
77
77
78 def do_hgweb(self):
78 def do_hgweb(self):
79 path, query = _splitURI(self.path)
79 path, query = _splitURI(self.path)
80
80
81 env = {}
81 env = {}
82 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
82 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
83 env['REQUEST_METHOD'] = self.command
83 env['REQUEST_METHOD'] = self.command
84 env['SERVER_NAME'] = self.server.server_name
84 env['SERVER_NAME'] = self.server.server_name
85 env['SERVER_PORT'] = str(self.server.server_port)
85 env['SERVER_PORT'] = str(self.server.server_port)
86 env['REQUEST_URI'] = self.path
86 env['REQUEST_URI'] = self.path
87 env['SCRIPT_NAME'] = self.server.prefix
87 env['SCRIPT_NAME'] = self.server.prefix
88 env['PATH_INFO'] = path[len(self.server.prefix):]
88 env['PATH_INFO'] = path[len(self.server.prefix):]
89 env['REMOTE_HOST'] = self.client_address[0]
89 env['REMOTE_HOST'] = self.client_address[0]
90 env['REMOTE_ADDR'] = self.client_address[0]
90 env['REMOTE_ADDR'] = self.client_address[0]
91 if query:
91 if query:
92 env['QUERY_STRING'] = query
92 env['QUERY_STRING'] = query
93
93
94 if self.headers.typeheader is None:
94 if self.headers.typeheader is None:
95 env['CONTENT_TYPE'] = self.headers.type
95 env['CONTENT_TYPE'] = self.headers.type
96 else:
96 else:
97 env['CONTENT_TYPE'] = self.headers.typeheader
97 env['CONTENT_TYPE'] = self.headers.typeheader
98 length = self.headers.getheader('content-length')
98 length = self.headers.getheader('content-length')
99 if length:
99 if length:
100 env['CONTENT_LENGTH'] = length
100 env['CONTENT_LENGTH'] = length
101 for header in [h for h in self.headers.keys()
101 for header in [h for h in self.headers.keys()
102 if h not in ('content-type', 'content-length')]:
102 if h not in ('content-type', 'content-length')]:
103 hkey = 'HTTP_' + header.replace('-', '_').upper()
103 hkey = 'HTTP_' + header.replace('-', '_').upper()
104 hval = self.headers.getheader(header)
104 hval = self.headers.getheader(header)
105 hval = hval.replace('\n', '').strip()
105 hval = hval.replace('\n', '').strip()
106 if hval:
106 if hval:
107 env[hkey] = hval
107 env[hkey] = hval
108 env['SERVER_PROTOCOL'] = self.request_version
108 env['SERVER_PROTOCOL'] = self.request_version
109 env['wsgi.version'] = (1, 0)
109 env['wsgi.version'] = (1, 0)
110 env['wsgi.url_scheme'] = self.url_scheme
110 env['wsgi.url_scheme'] = self.url_scheme
111 env['wsgi.input'] = self.rfile
111 env['wsgi.input'] = self.rfile
112 env['wsgi.errors'] = _error_logger(self)
112 env['wsgi.errors'] = _error_logger(self)
113 env['wsgi.multithread'] = isinstance(self.server,
113 env['wsgi.multithread'] = isinstance(self.server,
114 SocketServer.ThreadingMixIn)
114 SocketServer.ThreadingMixIn)
115 env['wsgi.multiprocess'] = isinstance(self.server,
115 env['wsgi.multiprocess'] = isinstance(self.server,
116 SocketServer.ForkingMixIn)
116 SocketServer.ForkingMixIn)
117 env['wsgi.run_once'] = 0
117 env['wsgi.run_once'] = 0
118
118
119 self.close_connection = True
119 self.close_connection = True
120 self.saved_status = None
120 self.saved_status = None
121 self.saved_headers = []
121 self.saved_headers = []
122 self.sent_headers = False
122 self.sent_headers = False
123 self.length = None
123 self.length = None
124 for chunk in self.server.application(env, self._start_response):
124 for chunk in self.server.application(env, self._start_response):
125 self._write(chunk)
125 self._write(chunk)
126
126
127 def send_headers(self):
127 def send_headers(self):
128 if not self.saved_status:
128 if not self.saved_status:
129 raise AssertionError("Sending headers before start_response() called")
129 raise AssertionError("Sending headers before start_response() called")
130 saved_status = self.saved_status.split(None, 1)
130 saved_status = self.saved_status.split(None, 1)
131 saved_status[0] = int(saved_status[0])
131 saved_status[0] = int(saved_status[0])
132 self.send_response(*saved_status)
132 self.send_response(*saved_status)
133 should_close = True
133 should_close = True
134 for h in self.saved_headers:
134 for h in self.saved_headers:
135 self.send_header(*h)
135 self.send_header(*h)
136 if h[0].lower() == 'content-length':
136 if h[0].lower() == 'content-length':
137 should_close = False
137 should_close = False
138 self.length = int(h[1])
138 self.length = int(h[1])
139 # The value of the Connection header is a list of case-insensitive
139 # The value of the Connection header is a list of case-insensitive
140 # tokens separated by commas and optional whitespace.
140 # tokens separated by commas and optional whitespace.
141 if 'close' in [token.strip().lower() for token in
141 if 'close' in [token.strip().lower() for token in
142 self.headers.get('connection', '').split(',')]:
142 self.headers.get('connection', '').split(',')]:
143 should_close = True
143 should_close = True
144 if should_close:
144 if should_close:
145 self.send_header('Connection', 'close')
145 self.send_header('Connection', 'close')
146 self.close_connection = should_close
146 self.close_connection = should_close
147 self.end_headers()
147 self.end_headers()
148 self.sent_headers = True
148 self.sent_headers = True
149
149
150 def _start_response(self, http_status, headers, exc_info=None):
150 def _start_response(self, http_status, headers, exc_info=None):
151 code, msg = http_status.split(None, 1)
151 code, msg = http_status.split(None, 1)
152 code = int(code)
152 code = int(code)
153 self.saved_status = http_status
153 self.saved_status = http_status
154 bad_headers = ('connection', 'transfer-encoding')
154 bad_headers = ('connection', 'transfer-encoding')
155 self.saved_headers = [h for h in headers
155 self.saved_headers = [h for h in headers
156 if h[0].lower() not in bad_headers]
156 if h[0].lower() not in bad_headers]
157 return self._write
157 return self._write
158
158
159 def _write(self, data):
159 def _write(self, data):
160 if not self.saved_status:
160 if not self.saved_status:
161 raise AssertionError("data written before start_response() called")
161 raise AssertionError("data written before start_response() called")
162 elif not self.sent_headers:
162 elif not self.sent_headers:
163 self.send_headers()
163 self.send_headers()
164 if self.length is not None:
164 if self.length is not None:
165 if len(data) > self.length:
165 if len(data) > self.length:
166 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
166 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
167 self.length = self.length - len(data)
167 self.length = self.length - len(data)
168 self.wfile.write(data)
168 self.wfile.write(data)
169 self.wfile.flush()
169 self.wfile.flush()
170
170
171 class _shgwebhandler(_hgwebhandler):
171 class _shgwebhandler(_hgwebhandler):
172
172
173 url_scheme = 'https'
173 url_scheme = 'https'
174
174
175 def setup(self):
175 def setup(self):
176 self.connection = self.request
176 self.connection = self.request
177 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
177 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
178 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
178 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
179
179
180 def do_write(self):
180 def do_write(self):
181 from OpenSSL.SSL import SysCallError
181 from OpenSSL.SSL import SysCallError
182 try:
182 try:
183 super(_shgwebhandler, self).do_write()
183 super(_shgwebhandler, self).do_write()
184 except SysCallError, inst:
184 except SysCallError, inst:
185 if inst.args[0] != errno.EPIPE:
185 if inst.args[0] != errno.EPIPE:
186 raise
186 raise
187
187
188 def handle_one_request(self):
188 def handle_one_request(self):
189 from OpenSSL.SSL import SysCallError, ZeroReturnError
189 from OpenSSL.SSL import SysCallError, ZeroReturnError
190 try:
190 try:
191 super(_shgwebhandler, self).handle_one_request()
191 super(_shgwebhandler, self).handle_one_request()
192 except (SysCallError, ZeroReturnError):
192 except (SysCallError, ZeroReturnError):
193 self.close_connection = True
193 self.close_connection = True
194 pass
194 pass
195
195
196 def create_server(ui, repo):
196 def create_server(ui, repo):
197 use_threads = True
197 use_threads = True
198
198
199 def openlog(opt, default):
199 def openlog(opt, default):
200 if opt and opt != '-':
200 if opt and opt != '-':
201 return open(opt, 'a')
201 return open(opt, 'a')
202 return default
202 return default
203
203
204 if repo is None:
204 if repo is None:
205 myui = ui
205 myui = ui
206 else:
206 else:
207 myui = repo.ui
207 myui = repo.ui
208 address = myui.config("web", "address", "")
208 address = myui.config("web", "address", "")
209 port = int(myui.config("web", "port", 8000))
209 port = int(myui.config("web", "port", 8000))
210 prefix = myui.config("web", "prefix", "")
210 prefix = myui.config("web", "prefix", "")
211 if prefix:
211 if prefix:
212 prefix = "/" + prefix.strip("/")
212 prefix = "/" + prefix.strip("/")
213 use_ipv6 = myui.configbool("web", "ipv6")
213 use_ipv6 = myui.configbool("web", "ipv6")
214 webdir_conf = myui.config("web", "webdir_conf")
214 webdir_conf = myui.config("web", "webdir_conf")
215 ssl_cert = myui.config("web", "certificate")
215 ssl_cert = myui.config("web", "certificate")
216 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
216 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
217 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
217 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
218
218
219 if use_threads:
219 if use_threads:
220 try:
220 try:
221 from threading import activeCount
221 from threading import activeCount
222 except ImportError:
222 except ImportError:
223 use_threads = False
223 use_threads = False
224
224
225 if use_threads:
225 if use_threads:
226 _mixin = SocketServer.ThreadingMixIn
226 _mixin = SocketServer.ThreadingMixIn
227 else:
227 else:
228 if hasattr(os, "fork"):
228 if hasattr(os, "fork"):
229 _mixin = SocketServer.ForkingMixIn
229 _mixin = SocketServer.ForkingMixIn
230 else:
230 else:
231 class _mixin:
231 class _mixin:
232 pass
232 pass
233
233
234 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
234 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
235
235
236 # SO_REUSEADDR has broken semantics on windows
236 # SO_REUSEADDR has broken semantics on windows
237 if os.name == 'nt':
237 if os.name == 'nt':
238 allow_reuse_address = 0
238 allow_reuse_address = 0
239
239
240 def __init__(self, *args, **kargs):
240 def __init__(self, *args, **kargs):
241 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
241 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
242 self.accesslog = accesslog
242 self.accesslog = accesslog
243 self.errorlog = errorlog
243 self.errorlog = errorlog
244 self.daemon_threads = True
244 self.daemon_threads = True
245 def make_handler():
245 def make_handler():
246 if webdir_conf:
246 if webdir_conf:
247 hgwebobj = hgwebdir(webdir_conf, ui)
247 hgwebobj = hgwebdir(webdir_conf, ui)
248 elif repo is not None:
248 elif repo is not None:
249 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
249 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
250 else:
250 else:
251 raise error.RepoError(_("There is no Mercurial repository"
251 raise error.RepoError(_("There is no Mercurial repository"
252 " here (.hg not found)"))
252 " here (.hg not found)"))
253 return hgwebobj
253 return hgwebobj
254 self.application = make_handler()
254 self.application = make_handler()
255
255
256 if ssl_cert:
256 if ssl_cert:
257 try:
257 try:
258 from OpenSSL import SSL
258 from OpenSSL import SSL
259 ctx = SSL.Context(SSL.SSLv23_METHOD)
259 ctx = SSL.Context(SSL.SSLv23_METHOD)
260 except ImportError:
260 except ImportError:
261 raise util.Abort(_("SSL support is unavailable"))
261 raise util.Abort(_("SSL support is unavailable"))
262 ctx.use_privatekey_file(ssl_cert)
262 ctx.use_privatekey_file(ssl_cert)
263 ctx.use_certificate_file(ssl_cert)
263 ctx.use_certificate_file(ssl_cert)
264 sock = socket.socket(self.address_family, self.socket_type)
264 sock = socket.socket(self.address_family, self.socket_type)
265 self.socket = SSL.Connection(ctx, sock)
265 self.socket = SSL.Connection(ctx, sock)
266 self.server_bind()
266 self.server_bind()
267 self.server_activate()
267 self.server_activate()
268
268
269 self.addr, self.port = self.socket.getsockname()[0:2]
269 self.addr, self.port = self.socket.getsockname()[0:2]
270 self.prefix = prefix
270 self.prefix = prefix
271 self.fqaddr = socket.getfqdn(address)
271 self.fqaddr = socket.getfqdn(address)
272
272
273 class IPv6HTTPServer(MercurialHTTPServer):
273 class IPv6HTTPServer(MercurialHTTPServer):
274 address_family = getattr(socket, 'AF_INET6', None)
274 address_family = getattr(socket, 'AF_INET6', None)
275
275
276 def __init__(self, *args, **kwargs):
276 def __init__(self, *args, **kwargs):
277 if self.address_family is None:
277 if self.address_family is None:
278 raise error.RepoError(_('IPv6 is not available on this system'))
278 raise error.RepoError(_('IPv6 is not available on this system'))
279 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
279 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
280
280
281 if ssl_cert:
281 if ssl_cert:
282 handler = _shgwebhandler
282 handler = _shgwebhandler
283 else:
283 else:
284 handler = _hgwebhandler
284 handler = _hgwebhandler
285
285
286 # ugly hack due to python issue5853 (for threaded use)
287 import mimetypes; mimetypes.init()
288
286 try:
289 try:
287 if use_ipv6:
290 if use_ipv6:
288 return IPv6HTTPServer((address, port), handler)
291 return IPv6HTTPServer((address, port), handler)
289 else:
292 else:
290 return MercurialHTTPServer((address, port), handler)
293 return MercurialHTTPServer((address, port), handler)
291 except socket.error, inst:
294 except socket.error, inst:
292 raise util.Abort(_("cannot start server at '%s:%d': %s")
295 raise util.Abort(_("cannot start server at '%s:%d': %s")
293 % (address, port, inst.args[1]))
296 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now