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