##// END OF EJS Templates
server: externalize and streamline mixin setup
Dirkjan Ochtman -
r10639:a6808629 default
parent child Browse files
Show More
@@ -1,297 +1,291
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 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(BaseHTTPServer.BaseHTTPRequestHandler):
38 class _hgwebhandler(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 "
72 self.log_error("Exception happened during processing "
73 "request '%s':\n%s", self.path, tb)
73 "request '%s':\n%s", 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 "
129 raise AssertionError("Sending headers before "
130 "start_response() called")
130 "start_response() called")
131 saved_status = self.saved_status.split(None, 1)
131 saved_status = self.saved_status.split(None, 1)
132 saved_status[0] = int(saved_status[0])
132 saved_status[0] = int(saved_status[0])
133 self.send_response(*saved_status)
133 self.send_response(*saved_status)
134 should_close = True
134 should_close = True
135 for h in self.saved_headers:
135 for h in self.saved_headers:
136 self.send_header(*h)
136 self.send_header(*h)
137 if h[0].lower() == 'content-length':
137 if h[0].lower() == 'content-length':
138 should_close = False
138 should_close = False
139 self.length = int(h[1])
139 self.length = int(h[1])
140 # The value of the Connection header is a list of case-insensitive
140 # The value of the Connection header is a list of case-insensitive
141 # tokens separated by commas and optional whitespace.
141 # tokens separated by commas and optional whitespace.
142 if 'close' in [token.strip().lower() for token in
142 if 'close' in [token.strip().lower() for token in
143 self.headers.get('connection', '').split(',')]:
143 self.headers.get('connection', '').split(',')]:
144 should_close = True
144 should_close = True
145 if should_close:
145 if should_close:
146 self.send_header('Connection', 'close')
146 self.send_header('Connection', 'close')
147 self.close_connection = should_close
147 self.close_connection = should_close
148 self.end_headers()
148 self.end_headers()
149 self.sent_headers = True
149 self.sent_headers = True
150
150
151 def _start_response(self, http_status, headers, exc_info=None):
151 def _start_response(self, http_status, headers, exc_info=None):
152 code, msg = http_status.split(None, 1)
152 code, msg = http_status.split(None, 1)
153 code = int(code)
153 code = int(code)
154 self.saved_status = http_status
154 self.saved_status = http_status
155 bad_headers = ('connection', 'transfer-encoding')
155 bad_headers = ('connection', 'transfer-encoding')
156 self.saved_headers = [h for h in headers
156 self.saved_headers = [h for h in headers
157 if h[0].lower() not in bad_headers]
157 if h[0].lower() not in bad_headers]
158 return self._write
158 return self._write
159
159
160 def _write(self, data):
160 def _write(self, data):
161 if not self.saved_status:
161 if not self.saved_status:
162 raise AssertionError("data written before start_response() called")
162 raise AssertionError("data written before start_response() called")
163 elif not self.sent_headers:
163 elif not self.sent_headers:
164 self.send_headers()
164 self.send_headers()
165 if self.length is not None:
165 if self.length is not None:
166 if len(data) > self.length:
166 if len(data) > self.length:
167 raise AssertionError("Content-length header sent, but more "
167 raise AssertionError("Content-length header sent, but more "
168 "bytes than specified are being written.")
168 "bytes than specified are being written.")
169 self.length = self.length - len(data)
169 self.length = self.length - len(data)
170 self.wfile.write(data)
170 self.wfile.write(data)
171 self.wfile.flush()
171 self.wfile.flush()
172
172
173 class _shgwebhandler(_hgwebhandler):
173 class _shgwebhandler(_hgwebhandler):
174
174
175 url_scheme = 'https'
175 url_scheme = 'https'
176
176
177 def setup(self):
177 def setup(self):
178 self.connection = self.request
178 self.connection = self.request
179 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
179 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
180 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
180 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
181
181
182 def do_write(self):
182 def do_write(self):
183 from OpenSSL.SSL import SysCallError
183 from OpenSSL.SSL import SysCallError
184 try:
184 try:
185 super(_shgwebhandler, self).do_write()
185 super(_shgwebhandler, self).do_write()
186 except SysCallError, inst:
186 except SysCallError, inst:
187 if inst.args[0] != errno.EPIPE:
187 if inst.args[0] != errno.EPIPE:
188 raise
188 raise
189
189
190 def handle_one_request(self):
190 def handle_one_request(self):
191 from OpenSSL.SSL import SysCallError, ZeroReturnError
191 from OpenSSL.SSL import SysCallError, ZeroReturnError
192 try:
192 try:
193 super(_shgwebhandler, self).handle_one_request()
193 super(_shgwebhandler, self).handle_one_request()
194 except (SysCallError, ZeroReturnError):
194 except (SysCallError, ZeroReturnError):
195 self.close_connection = True
195 self.close_connection = True
196 pass
196 pass
197
197
198 try:
199 from threading import activeCount
200 _mixin = SocketServer.ThreadingMixIn
201 except ImportError:
202 if hasattr(os, "fork"):
203 _mixin = SocketServer.ForkingMixIn
204 else:
205 class _mixin:
206 pass
207
198 def create_server(ui, repo):
208 def create_server(ui, repo):
199 use_threads = True
200
209
201 def openlog(opt, default):
210 def openlog(opt, default):
202 if opt and opt != '-':
211 if opt and opt != '-':
203 return open(opt, 'a')
212 return open(opt, 'a')
204 return default
213 return default
205
214
206 if repo is None:
215 if repo is None:
207 myui = ui
216 myui = ui
208 else:
217 else:
209 myui = repo.ui
218 myui = repo.ui
210 address = myui.config("web", "address", "")
219 address = myui.config("web", "address", "")
211 port = int(myui.config("web", "port", 8000))
220 port = int(myui.config("web", "port", 8000))
212 prefix = myui.config("web", "prefix", "")
221 prefix = myui.config("web", "prefix", "")
213 if prefix:
222 if prefix:
214 prefix = "/" + prefix.strip("/")
223 prefix = "/" + prefix.strip("/")
215 use_ipv6 = myui.configbool("web", "ipv6")
224 use_ipv6 = myui.configbool("web", "ipv6")
216 webdir_conf = myui.config("web", "webdir_conf")
225 webdir_conf = myui.config("web", "webdir_conf")
217 ssl_cert = myui.config("web", "certificate")
226 ssl_cert = myui.config("web", "certificate")
218 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
227 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
219 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
228 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
220
229
221 if use_threads:
222 try:
223 from threading import activeCount
224 except ImportError:
225 use_threads = False
226
227 if use_threads:
228 _mixin = SocketServer.ThreadingMixIn
229 else:
230 if hasattr(os, "fork"):
231 _mixin = SocketServer.ForkingMixIn
232 else:
233 class _mixin:
234 pass
235
236 if webdir_conf:
230 if webdir_conf:
237 hgwebobj = hgwebdir(webdir_conf, ui)
231 hgwebobj = hgwebdir(webdir_conf, ui)
238 elif repo is not None:
232 elif repo is not None:
239 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
233 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
240 else:
234 else:
241 raise error.RepoError(_("There is no Mercurial repository"
235 raise error.RepoError(_("There is no Mercurial repository"
242 " here (.hg not found)"))
236 " here (.hg not found)"))
243
237
244 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
238 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
245
239
246 # SO_REUSEADDR has broken semantics on windows
240 # SO_REUSEADDR has broken semantics on windows
247 if os.name == 'nt':
241 if os.name == 'nt':
248 allow_reuse_address = 0
242 allow_reuse_address = 0
249
243
250 def __init__(self, *args, **kargs):
244 def __init__(self, *args, **kargs):
251 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
245 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
252 self.accesslog = accesslog
246 self.accesslog = accesslog
253 self.errorlog = errorlog
247 self.errorlog = errorlog
254 self.daemon_threads = True
248 self.daemon_threads = True
255 self.application = hgwebobj
249 self.application = hgwebobj
256
250
257 if ssl_cert:
251 if ssl_cert:
258 try:
252 try:
259 from OpenSSL import SSL
253 from OpenSSL import SSL
260 ctx = SSL.Context(SSL.SSLv23_METHOD)
254 ctx = SSL.Context(SSL.SSLv23_METHOD)
261 except ImportError:
255 except ImportError:
262 raise util.Abort(_("SSL support is unavailable"))
256 raise util.Abort(_("SSL support is unavailable"))
263 ctx.use_privatekey_file(ssl_cert)
257 ctx.use_privatekey_file(ssl_cert)
264 ctx.use_certificate_file(ssl_cert)
258 ctx.use_certificate_file(ssl_cert)
265 sock = socket.socket(self.address_family, self.socket_type)
259 sock = socket.socket(self.address_family, self.socket_type)
266 self.socket = SSL.Connection(ctx, sock)
260 self.socket = SSL.Connection(ctx, sock)
267 self.server_bind()
261 self.server_bind()
268 self.server_activate()
262 self.server_activate()
269
263
270 self.addr, self.port = self.socket.getsockname()[0:2]
264 self.addr, self.port = self.socket.getsockname()[0:2]
271 self.prefix = prefix
265 self.prefix = prefix
272 self.fqaddr = socket.getfqdn(address)
266 self.fqaddr = socket.getfqdn(address)
273
267
274 class IPv6HTTPServer(MercurialHTTPServer):
268 class IPv6HTTPServer(MercurialHTTPServer):
275 address_family = getattr(socket, 'AF_INET6', None)
269 address_family = getattr(socket, 'AF_INET6', None)
276
270
277 def __init__(self, *args, **kwargs):
271 def __init__(self, *args, **kwargs):
278 if self.address_family is None:
272 if self.address_family is None:
279 raise error.RepoError(_('IPv6 is not available on this system'))
273 raise error.RepoError(_('IPv6 is not available on this system'))
280 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
274 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
281
275
282 if ssl_cert:
276 if ssl_cert:
283 handler = _shgwebhandler
277 handler = _shgwebhandler
284 else:
278 else:
285 handler = _hgwebhandler
279 handler = _hgwebhandler
286
280
287 # ugly hack due to python issue5853 (for threaded use)
281 # ugly hack due to python issue5853 (for threaded use)
288 import mimetypes; mimetypes.init()
282 import mimetypes; mimetypes.init()
289
283
290 try:
284 try:
291 if use_ipv6:
285 if use_ipv6:
292 return IPv6HTTPServer((address, port), handler)
286 return IPv6HTTPServer((address, port), handler)
293 else:
287 else:
294 return MercurialHTTPServer((address, port), handler)
288 return MercurialHTTPServer((address, port), handler)
295 except socket.error, inst:
289 except socket.error, inst:
296 raise util.Abort(_("cannot start server at '%s:%d': %s")
290 raise util.Abort(_("cannot start server at '%s:%d': %s")
297 % (address, port, inst.args[1]))
291 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now