##// END OF EJS Templates
serve: send response headers even if response has no body...
Mads Kiilerich -
r18349:c007e5c5 default
parent child Browse files
Show More
@@ -1,320 +1,322
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
132 self.close_connection = True
133 self.saved_status = None
133 self.saved_status = None
134 self.saved_headers = []
134 self.saved_headers = []
135 self.sent_headers = False
135 self.sent_headers = False
136 self.length = None
136 self.length = None
137 for chunk in self.server.application(env, self._start_response):
137 for chunk in self.server.application(env, self._start_response):
138 self._write(chunk)
138 self._write(chunk)
139 if not self.sent_headers:
140 self.send_headers()
139
141
140 def send_headers(self):
142 def send_headers(self):
141 if not self.saved_status:
143 if not self.saved_status:
142 raise AssertionError("Sending headers before "
144 raise AssertionError("Sending headers before "
143 "start_response() called")
145 "start_response() called")
144 saved_status = self.saved_status.split(None, 1)
146 saved_status = self.saved_status.split(None, 1)
145 saved_status[0] = int(saved_status[0])
147 saved_status[0] = int(saved_status[0])
146 self.send_response(*saved_status)
148 self.send_response(*saved_status)
147 should_close = True
149 should_close = True
148 for h in self.saved_headers:
150 for h in self.saved_headers:
149 self.send_header(*h)
151 self.send_header(*h)
150 if h[0].lower() == 'content-length':
152 if h[0].lower() == 'content-length':
151 should_close = False
153 should_close = False
152 self.length = int(h[1])
154 self.length = int(h[1])
153 # The value of the Connection header is a list of case-insensitive
155 # The value of the Connection header is a list of case-insensitive
154 # tokens separated by commas and optional whitespace.
156 # tokens separated by commas and optional whitespace.
155 if 'close' in [token.strip().lower() for token in
157 if 'close' in [token.strip().lower() for token in
156 self.headers.get('connection', '').split(',')]:
158 self.headers.get('connection', '').split(',')]:
157 should_close = True
159 should_close = True
158 if should_close:
160 if should_close:
159 self.send_header('Connection', 'close')
161 self.send_header('Connection', 'close')
160 self.close_connection = should_close
162 self.close_connection = should_close
161 self.end_headers()
163 self.end_headers()
162 self.sent_headers = True
164 self.sent_headers = True
163
165
164 def _start_response(self, http_status, headers, exc_info=None):
166 def _start_response(self, http_status, headers, exc_info=None):
165 code, msg = http_status.split(None, 1)
167 code, msg = http_status.split(None, 1)
166 code = int(code)
168 code = int(code)
167 self.saved_status = http_status
169 self.saved_status = http_status
168 bad_headers = ('connection', 'transfer-encoding')
170 bad_headers = ('connection', 'transfer-encoding')
169 self.saved_headers = [h for h in headers
171 self.saved_headers = [h for h in headers
170 if h[0].lower() not in bad_headers]
172 if h[0].lower() not in bad_headers]
171 return self._write
173 return self._write
172
174
173 def _write(self, data):
175 def _write(self, data):
174 if not self.saved_status:
176 if not self.saved_status:
175 raise AssertionError("data written before start_response() called")
177 raise AssertionError("data written before start_response() called")
176 elif not self.sent_headers:
178 elif not self.sent_headers:
177 self.send_headers()
179 self.send_headers()
178 if self.length is not None:
180 if self.length is not None:
179 if len(data) > self.length:
181 if len(data) > self.length:
180 raise AssertionError("Content-length header sent, but more "
182 raise AssertionError("Content-length header sent, but more "
181 "bytes than specified are being written.")
183 "bytes than specified are being written.")
182 self.length = self.length - len(data)
184 self.length = self.length - len(data)
183 self.wfile.write(data)
185 self.wfile.write(data)
184 self.wfile.flush()
186 self.wfile.flush()
185
187
186 class _httprequesthandleropenssl(_httprequesthandler):
188 class _httprequesthandleropenssl(_httprequesthandler):
187 """HTTPS handler based on pyOpenSSL"""
189 """HTTPS handler based on pyOpenSSL"""
188
190
189 url_scheme = 'https'
191 url_scheme = 'https'
190
192
191 @staticmethod
193 @staticmethod
192 def preparehttpserver(httpserver, ssl_cert):
194 def preparehttpserver(httpserver, ssl_cert):
193 try:
195 try:
194 import OpenSSL
196 import OpenSSL
195 OpenSSL.SSL.Context
197 OpenSSL.SSL.Context
196 except ImportError:
198 except ImportError:
197 raise util.Abort(_("SSL support is unavailable"))
199 raise util.Abort(_("SSL support is unavailable"))
198 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
200 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
199 ctx.use_privatekey_file(ssl_cert)
201 ctx.use_privatekey_file(ssl_cert)
200 ctx.use_certificate_file(ssl_cert)
202 ctx.use_certificate_file(ssl_cert)
201 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
203 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
202 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
204 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
203 httpserver.server_bind()
205 httpserver.server_bind()
204 httpserver.server_activate()
206 httpserver.server_activate()
205
207
206 def setup(self):
208 def setup(self):
207 self.connection = self.request
209 self.connection = self.request
208 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
210 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
209 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
211 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
210
212
211 def do_write(self):
213 def do_write(self):
212 import OpenSSL
214 import OpenSSL
213 try:
215 try:
214 _httprequesthandler.do_write(self)
216 _httprequesthandler.do_write(self)
215 except OpenSSL.SSL.SysCallError, inst:
217 except OpenSSL.SSL.SysCallError, inst:
216 if inst.args[0] != errno.EPIPE:
218 if inst.args[0] != errno.EPIPE:
217 raise
219 raise
218
220
219 def handle_one_request(self):
221 def handle_one_request(self):
220 import OpenSSL
222 import OpenSSL
221 try:
223 try:
222 _httprequesthandler.handle_one_request(self)
224 _httprequesthandler.handle_one_request(self)
223 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
225 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
224 self.close_connection = True
226 self.close_connection = True
225 pass
227 pass
226
228
227 class _httprequesthandlerssl(_httprequesthandler):
229 class _httprequesthandlerssl(_httprequesthandler):
228 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
230 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
229
231
230 url_scheme = 'https'
232 url_scheme = 'https'
231
233
232 @staticmethod
234 @staticmethod
233 def preparehttpserver(httpserver, ssl_cert):
235 def preparehttpserver(httpserver, ssl_cert):
234 try:
236 try:
235 import ssl
237 import ssl
236 ssl.wrap_socket
238 ssl.wrap_socket
237 except ImportError:
239 except ImportError:
238 raise util.Abort(_("SSL support is unavailable"))
240 raise util.Abort(_("SSL support is unavailable"))
239 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
241 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
240 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
242 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
241
243
242 def setup(self):
244 def setup(self):
243 self.connection = self.request
245 self.connection = self.request
244 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
246 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
245 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
247 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
246
248
247 try:
249 try:
248 from threading import activeCount
250 from threading import activeCount
249 activeCount() # silence pyflakes
251 activeCount() # silence pyflakes
250 _mixin = SocketServer.ThreadingMixIn
252 _mixin = SocketServer.ThreadingMixIn
251 except ImportError:
253 except ImportError:
252 if util.safehasattr(os, "fork"):
254 if util.safehasattr(os, "fork"):
253 _mixin = SocketServer.ForkingMixIn
255 _mixin = SocketServer.ForkingMixIn
254 else:
256 else:
255 class _mixin(object):
257 class _mixin(object):
256 pass
258 pass
257
259
258 def openlog(opt, default):
260 def openlog(opt, default):
259 if opt and opt != '-':
261 if opt and opt != '-':
260 return open(opt, 'a')
262 return open(opt, 'a')
261 return default
263 return default
262
264
263 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
265 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
264
266
265 # SO_REUSEADDR has broken semantics on windows
267 # SO_REUSEADDR has broken semantics on windows
266 if os.name == 'nt':
268 if os.name == 'nt':
267 allow_reuse_address = 0
269 allow_reuse_address = 0
268
270
269 def __init__(self, ui, app, addr, handler, **kwargs):
271 def __init__(self, ui, app, addr, handler, **kwargs):
270 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
272 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
271 self.daemon_threads = True
273 self.daemon_threads = True
272 self.application = app
274 self.application = app
273
275
274 handler.preparehttpserver(self, ui.config('web', 'certificate'))
276 handler.preparehttpserver(self, ui.config('web', 'certificate'))
275
277
276 prefix = ui.config('web', 'prefix', '')
278 prefix = ui.config('web', 'prefix', '')
277 if prefix:
279 if prefix:
278 prefix = '/' + prefix.strip('/')
280 prefix = '/' + prefix.strip('/')
279 self.prefix = prefix
281 self.prefix = prefix
280
282
281 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
283 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
282 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
284 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
283 self.accesslog = alog
285 self.accesslog = alog
284 self.errorlog = elog
286 self.errorlog = elog
285
287
286 self.addr, self.port = self.socket.getsockname()[0:2]
288 self.addr, self.port = self.socket.getsockname()[0:2]
287 self.fqaddr = socket.getfqdn(addr[0])
289 self.fqaddr = socket.getfqdn(addr[0])
288
290
289 class IPv6HTTPServer(MercurialHTTPServer):
291 class IPv6HTTPServer(MercurialHTTPServer):
290 address_family = getattr(socket, 'AF_INET6', None)
292 address_family = getattr(socket, 'AF_INET6', None)
291 def __init__(self, *args, **kwargs):
293 def __init__(self, *args, **kwargs):
292 if self.address_family is None:
294 if self.address_family is None:
293 raise error.RepoError(_('IPv6 is not available on this system'))
295 raise error.RepoError(_('IPv6 is not available on this system'))
294 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
296 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
295
297
296 def create_server(ui, app):
298 def create_server(ui, app):
297
299
298 if ui.config('web', 'certificate'):
300 if ui.config('web', 'certificate'):
299 if sys.version_info >= (2, 6):
301 if sys.version_info >= (2, 6):
300 handler = _httprequesthandlerssl
302 handler = _httprequesthandlerssl
301 else:
303 else:
302 handler = _httprequesthandleropenssl
304 handler = _httprequesthandleropenssl
303 else:
305 else:
304 handler = _httprequesthandler
306 handler = _httprequesthandler
305
307
306 if ui.configbool('web', 'ipv6'):
308 if ui.configbool('web', 'ipv6'):
307 cls = IPv6HTTPServer
309 cls = IPv6HTTPServer
308 else:
310 else:
309 cls = MercurialHTTPServer
311 cls = MercurialHTTPServer
310
312
311 # ugly hack due to python issue5853 (for threaded use)
313 # ugly hack due to python issue5853 (for threaded use)
312 import mimetypes; mimetypes.init()
314 import mimetypes; mimetypes.init()
313
315
314 address = ui.config('web', 'address', '')
316 address = ui.config('web', 'address', '')
315 port = util.getport(ui.config('web', 'port', 8000))
317 port = util.getport(ui.config('web', 'port', 8000))
316 try:
318 try:
317 return cls(ui, app, (address, port), handler)
319 return cls(ui, app, (address, port), handler)
318 except socket.error, inst:
320 except socket.error, inst:
319 raise util.Abort(_("cannot start server at '%s:%d': %s")
321 raise util.Abort(_("cannot start server at '%s:%d': %s")
320 % (address, port, inst.args[1]))
322 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now