##// END OF EJS Templates
hgweb: log headers only if headers were successfully parsed...
David Soria Parra -
r19877:52ed85d9 stable
parent child Browse files
Show More
@@ -1,330 +1,333
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 xheaders = [h for h in self.headers.items() if h[0].startswith('x-')]
63 xheaders = []
64 if util.safehasattr(self, 'headers'):
65 xheaders = [h for h in self.headers.items()
66 if h[0].startswith('x-')]
64 67 self.log_message('"%s" %s %s%s',
65 68 self.requestline, str(code), str(size),
66 69 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
67 70
68 71 def do_write(self):
69 72 try:
70 73 self.do_hgweb()
71 74 except socket.error, inst:
72 75 if inst[0] != errno.EPIPE:
73 76 raise
74 77
75 78 def do_POST(self):
76 79 try:
77 80 self.do_write()
78 81 except Exception:
79 82 self._start_response("500 Internal Server Error", [])
80 83 self._write("Internal Server Error")
81 84 tb = "".join(traceback.format_exception(*sys.exc_info()))
82 85 self.log_error("Exception happened during processing "
83 86 "request '%s':\n%s", self.path, tb)
84 87
85 88 def do_GET(self):
86 89 self.do_POST()
87 90
88 91 def do_hgweb(self):
89 92 path, query = _splitURI(self.path)
90 93
91 94 env = {}
92 95 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
93 96 env['REQUEST_METHOD'] = self.command
94 97 env['SERVER_NAME'] = self.server.server_name
95 98 env['SERVER_PORT'] = str(self.server.server_port)
96 99 env['REQUEST_URI'] = self.path
97 100 env['SCRIPT_NAME'] = self.server.prefix
98 101 env['PATH_INFO'] = path[len(self.server.prefix):]
99 102 env['REMOTE_HOST'] = self.client_address[0]
100 103 env['REMOTE_ADDR'] = self.client_address[0]
101 104 if query:
102 105 env['QUERY_STRING'] = query
103 106
104 107 if self.headers.typeheader is None:
105 108 env['CONTENT_TYPE'] = self.headers.type
106 109 else:
107 110 env['CONTENT_TYPE'] = self.headers.typeheader
108 111 length = self.headers.getheader('content-length')
109 112 if length:
110 113 env['CONTENT_LENGTH'] = length
111 114 for header in [h for h in self.headers.keys()
112 115 if h not in ('content-type', 'content-length')]:
113 116 hkey = 'HTTP_' + header.replace('-', '_').upper()
114 117 hval = self.headers.getheader(header)
115 118 hval = hval.replace('\n', '').strip()
116 119 if hval:
117 120 env[hkey] = hval
118 121 env['SERVER_PROTOCOL'] = self.request_version
119 122 env['wsgi.version'] = (1, 0)
120 123 env['wsgi.url_scheme'] = self.url_scheme
121 124 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
122 125 self.rfile = common.continuereader(self.rfile, self.wfile.write)
123 126
124 127 env['wsgi.input'] = self.rfile
125 128 env['wsgi.errors'] = _error_logger(self)
126 129 env['wsgi.multithread'] = isinstance(self.server,
127 130 SocketServer.ThreadingMixIn)
128 131 env['wsgi.multiprocess'] = isinstance(self.server,
129 132 SocketServer.ForkingMixIn)
130 133 env['wsgi.run_once'] = 0
131 134
132 135 self.saved_status = None
133 136 self.saved_headers = []
134 137 self.sent_headers = False
135 138 self.length = None
136 139 self._chunked = None
137 140 for chunk in self.server.application(env, self._start_response):
138 141 self._write(chunk)
139 142 if not self.sent_headers:
140 143 self.send_headers()
141 144 self._done()
142 145
143 146 def send_headers(self):
144 147 if not self.saved_status:
145 148 raise AssertionError("Sending headers before "
146 149 "start_response() called")
147 150 saved_status = self.saved_status.split(None, 1)
148 151 saved_status[0] = int(saved_status[0])
149 152 self.send_response(*saved_status)
150 153 self.length = None
151 154 self._chunked = False
152 155 for h in self.saved_headers:
153 156 self.send_header(*h)
154 157 if h[0].lower() == 'content-length':
155 158 self.length = int(h[1])
156 159 if (self.length is None and
157 160 saved_status[0] != common.HTTP_NOT_MODIFIED):
158 161 self._chunked = (not self.close_connection and
159 162 self.request_version == "HTTP/1.1")
160 163 if self._chunked:
161 164 self.send_header('Transfer-Encoding', 'chunked')
162 165 else:
163 166 self.send_header('Connection', 'close')
164 167 self.end_headers()
165 168 self.sent_headers = True
166 169
167 170 def _start_response(self, http_status, headers, exc_info=None):
168 171 code, msg = http_status.split(None, 1)
169 172 code = int(code)
170 173 self.saved_status = http_status
171 174 bad_headers = ('connection', 'transfer-encoding')
172 175 self.saved_headers = [h for h in headers
173 176 if h[0].lower() not in bad_headers]
174 177 return self._write
175 178
176 179 def _write(self, data):
177 180 if not self.saved_status:
178 181 raise AssertionError("data written before start_response() called")
179 182 elif not self.sent_headers:
180 183 self.send_headers()
181 184 if self.length is not None:
182 185 if len(data) > self.length:
183 186 raise AssertionError("Content-length header sent, but more "
184 187 "bytes than specified are being written.")
185 188 self.length = self.length - len(data)
186 189 elif self._chunked and data:
187 190 data = '%x\r\n%s\r\n' % (len(data), data)
188 191 self.wfile.write(data)
189 192 self.wfile.flush()
190 193
191 194 def _done(self):
192 195 if self._chunked:
193 196 self.wfile.write('0\r\n\r\n')
194 197 self.wfile.flush()
195 198
196 199 class _httprequesthandleropenssl(_httprequesthandler):
197 200 """HTTPS handler based on pyOpenSSL"""
198 201
199 202 url_scheme = 'https'
200 203
201 204 @staticmethod
202 205 def preparehttpserver(httpserver, ssl_cert):
203 206 try:
204 207 import OpenSSL
205 208 OpenSSL.SSL.Context
206 209 except ImportError:
207 210 raise util.Abort(_("SSL support is unavailable"))
208 211 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
209 212 ctx.use_privatekey_file(ssl_cert)
210 213 ctx.use_certificate_file(ssl_cert)
211 214 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
212 215 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
213 216 httpserver.server_bind()
214 217 httpserver.server_activate()
215 218
216 219 def setup(self):
217 220 self.connection = self.request
218 221 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
219 222 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
220 223
221 224 def do_write(self):
222 225 import OpenSSL
223 226 try:
224 227 _httprequesthandler.do_write(self)
225 228 except OpenSSL.SSL.SysCallError, inst:
226 229 if inst.args[0] != errno.EPIPE:
227 230 raise
228 231
229 232 def handle_one_request(self):
230 233 import OpenSSL
231 234 try:
232 235 _httprequesthandler.handle_one_request(self)
233 236 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
234 237 self.close_connection = True
235 238 pass
236 239
237 240 class _httprequesthandlerssl(_httprequesthandler):
238 241 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
239 242
240 243 url_scheme = 'https'
241 244
242 245 @staticmethod
243 246 def preparehttpserver(httpserver, ssl_cert):
244 247 try:
245 248 import ssl
246 249 ssl.wrap_socket
247 250 except ImportError:
248 251 raise util.Abort(_("SSL support is unavailable"))
249 252 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
250 253 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
251 254
252 255 def setup(self):
253 256 self.connection = self.request
254 257 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
255 258 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
256 259
257 260 try:
258 261 from threading import activeCount
259 262 activeCount() # silence pyflakes
260 263 _mixin = SocketServer.ThreadingMixIn
261 264 except ImportError:
262 265 if util.safehasattr(os, "fork"):
263 266 _mixin = SocketServer.ForkingMixIn
264 267 else:
265 268 class _mixin(object):
266 269 pass
267 270
268 271 def openlog(opt, default):
269 272 if opt and opt != '-':
270 273 return open(opt, 'a')
271 274 return default
272 275
273 276 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
274 277
275 278 # SO_REUSEADDR has broken semantics on windows
276 279 if os.name == 'nt':
277 280 allow_reuse_address = 0
278 281
279 282 def __init__(self, ui, app, addr, handler, **kwargs):
280 283 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
281 284 self.daemon_threads = True
282 285 self.application = app
283 286
284 287 handler.preparehttpserver(self, ui.config('web', 'certificate'))
285 288
286 289 prefix = ui.config('web', 'prefix', '')
287 290 if prefix:
288 291 prefix = '/' + prefix.strip('/')
289 292 self.prefix = prefix
290 293
291 294 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
292 295 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
293 296 self.accesslog = alog
294 297 self.errorlog = elog
295 298
296 299 self.addr, self.port = self.socket.getsockname()[0:2]
297 300 self.fqaddr = socket.getfqdn(addr[0])
298 301
299 302 class IPv6HTTPServer(MercurialHTTPServer):
300 303 address_family = getattr(socket, 'AF_INET6', None)
301 304 def __init__(self, *args, **kwargs):
302 305 if self.address_family is None:
303 306 raise error.RepoError(_('IPv6 is not available on this system'))
304 307 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
305 308
306 309 def create_server(ui, app):
307 310
308 311 if ui.config('web', 'certificate'):
309 312 if sys.version_info >= (2, 6):
310 313 handler = _httprequesthandlerssl
311 314 else:
312 315 handler = _httprequesthandleropenssl
313 316 else:
314 317 handler = _httprequesthandler
315 318
316 319 if ui.configbool('web', 'ipv6'):
317 320 cls = IPv6HTTPServer
318 321 else:
319 322 cls = MercurialHTTPServer
320 323
321 324 # ugly hack due to python issue5853 (for threaded use)
322 325 import mimetypes; mimetypes.init()
323 326
324 327 address = ui.config('web', 'address', '')
325 328 port = util.getport(ui.config('web', 'port', 8000))
326 329 try:
327 330 return cls(ui, app, (address, port), handler)
328 331 except socket.error, inst:
329 332 raise util.Abort(_("cannot start server at '%s:%d': %s")
330 333 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now