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