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