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