##// END OF EJS Templates
cosmetics
Brendan Cully -
r4871:d787d9ad default
parent child Browse files
Show More
@@ -1,289 +1,289 b''
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
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
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 ui, hg, util, templater
10 from mercurial import ui, hg, util, templater
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 request import wsgiapplication
13 from request import wsgiapplication
14 from mercurial.i18n import gettext as _
14 from mercurial.i18n import gettext as _
15
15
16 def _splitURI(uri):
16 def _splitURI(uri):
17 """ Return path and query splited from uri
17 """ Return path and query splited from uri
18
18
19 Just like CGI environment, the path is unquoted, the query is
19 Just like CGI environment, the path is unquoted, the query is
20 not.
20 not.
21 """
21 """
22 if '?' in uri:
22 if '?' in uri:
23 path, query = uri.split('?', 1)
23 path, query = uri.split('?', 1)
24 else:
24 else:
25 path, query = uri, ''
25 path, query = uri, ''
26 return urllib.unquote(path), query
26 return urllib.unquote(path), query
27
27
28 class _error_logger(object):
28 class _error_logger(object):
29 def __init__(self, handler):
29 def __init__(self, handler):
30 self.handler = handler
30 self.handler = handler
31 def flush(self):
31 def flush(self):
32 pass
32 pass
33 def write(self, str):
33 def write(self, str):
34 self.writelines(str.split('\n'))
34 self.writelines(str.split('\n'))
35 def writelines(self, seq):
35 def writelines(self, seq):
36 for msg in seq:
36 for msg in seq:
37 self.handler.log_error("HG error: %s", msg)
37 self.handler.log_error("HG error: %s", msg)
38
38
39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
40
40
41 url_scheme = 'http'
41 url_scheme = 'http'
42
42
43 def __init__(self, *args, **kargs):
43 def __init__(self, *args, **kargs):
44 self.protocol_version = 'HTTP/1.1'
44 self.protocol_version = 'HTTP/1.1'
45 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
45 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
46
46
47 def log_error(self, format, *args):
47 def log_error(self, format, *args):
48 errorlog = self.server.errorlog
48 errorlog = self.server.errorlog
49 errorlog.write("%s - - [%s] %s\n" % (self.client_address[0],
49 errorlog.write("%s - - [%s] %s\n" % (self.client_address[0],
50 self.log_date_time_string(),
50 self.log_date_time_string(),
51 format % args))
51 format % args))
52
52
53 def log_message(self, format, *args):
53 def log_message(self, format, *args):
54 accesslog = self.server.accesslog
54 accesslog = self.server.accesslog
55 accesslog.write("%s - - [%s] %s\n" % (self.client_address[0],
55 accesslog.write("%s - - [%s] %s\n" % (self.client_address[0],
56 self.log_date_time_string(),
56 self.log_date_time_string(),
57 format % args))
57 format % args))
58
58
59 def do_write(self):
59 def do_write(self):
60 try:
60 try:
61 self.do_hgweb()
61 self.do_hgweb()
62 except socket.error, inst:
62 except socket.error, inst:
63 if inst[0] != errno.EPIPE:
63 if inst[0] != errno.EPIPE:
64 raise
64 raise
65
65
66 def do_POST(self):
66 def do_POST(self):
67 try:
67 try:
68 self.do_write()
68 self.do_write()
69 except StandardError, inst:
69 except StandardError, inst:
70 self._start_response("500 Internal Server Error", [])
70 self._start_response("500 Internal Server Error", [])
71 self._write("Internal Server Error")
71 self._write("Internal Server Error")
72 tb = "".join(traceback.format_exception(*sys.exc_info()))
72 tb = "".join(traceback.format_exception(*sys.exc_info()))
73 self.log_error("Exception happened during processing request '%s':\n%s",
73 self.log_error("Exception happened during processing request '%s':\n%s",
74 self.path, tb)
74 self.path, tb)
75
75
76 def do_GET(self):
76 def do_GET(self):
77 self.do_POST()
77 self.do_POST()
78
78
79 def do_hgweb(self):
79 def do_hgweb(self):
80 path_info, query = _splitURI(self.path)
80 path_info, query = _splitURI(self.path)
81
81
82 env = {}
82 env = {}
83 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
83 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
84 env['REQUEST_METHOD'] = self.command
84 env['REQUEST_METHOD'] = self.command
85 env['SERVER_NAME'] = self.server.server_name
85 env['SERVER_NAME'] = self.server.server_name
86 env['SERVER_PORT'] = str(self.server.server_port)
86 env['SERVER_PORT'] = str(self.server.server_port)
87 env['REQUEST_URI'] = self.path
87 env['REQUEST_URI'] = self.path
88 env['PATH_INFO'] = path_info
88 env['PATH_INFO'] = path_info
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'] = type(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 req = self.server.reqmaker(env, self._start_response)
124 req = self.server.reqmaker(env, self._start_response)
125 for data in req:
125 for data in req:
126 if data:
126 if data:
127 self._write(data)
127 self._write(data)
128
128
129 def send_headers(self):
129 def send_headers(self):
130 if not self.saved_status:
130 if not self.saved_status:
131 raise AssertionError("Sending headers before start_response() called")
131 raise AssertionError("Sending headers before start_response() called")
132 saved_status = self.saved_status.split(None, 1)
132 saved_status = self.saved_status.split(None, 1)
133 saved_status[0] = int(saved_status[0])
133 saved_status[0] = int(saved_status[0])
134 self.send_response(*saved_status)
134 self.send_response(*saved_status)
135 should_close = True
135 should_close = True
136 for h in self.saved_headers:
136 for h in self.saved_headers:
137 self.send_header(*h)
137 self.send_header(*h)
138 if h[0].lower() == 'content-length':
138 if h[0].lower() == 'content-length':
139 should_close = False
139 should_close = False
140 self.length = int(h[1])
140 self.length = int(h[1])
141 # The value of the Connection header is a list of case-insensitive
141 # The value of the Connection header is a list of case-insensitive
142 # tokens separated by commas and optional whitespace.
142 # tokens separated by commas and optional whitespace.
143 if 'close' in [token.strip().lower() for token in
143 if 'close' in [token.strip().lower() for token in
144 self.headers.get('connection', '').split(',')]:
144 self.headers.get('connection', '').split(',')]:
145 should_close = True
145 should_close = True
146 if should_close:
146 if should_close:
147 self.send_header('Connection', 'close')
147 self.send_header('Connection', 'close')
148 self.close_connection = should_close
148 self.close_connection = should_close
149 self.end_headers()
149 self.end_headers()
150 self.sent_headers = True
150 self.sent_headers = True
151
151
152 def _start_response(self, http_status, headers, exc_info=None):
152 def _start_response(self, http_status, headers, exc_info=None):
153 code, msg = http_status.split(None, 1)
153 code, msg = http_status.split(None, 1)
154 code = int(code)
154 code = int(code)
155 self.saved_status = http_status
155 self.saved_status = http_status
156 bad_headers = ('connection', 'transfer-encoding')
156 bad_headers = ('connection', 'transfer-encoding')
157 self.saved_headers = [h for h in headers
157 self.saved_headers = [h for h in headers
158 if h[0].lower() not in bad_headers]
158 if h[0].lower() not in bad_headers]
159 return self._write
159 return self._write
160
160
161 def _write(self, data):
161 def _write(self, data):
162 if not self.saved_status:
162 if not self.saved_status:
163 raise AssertionError("data written before start_response() called")
163 raise AssertionError("data written before start_response() called")
164 elif not self.sent_headers:
164 elif not self.sent_headers:
165 self.send_headers()
165 self.send_headers()
166 if self.length is not None:
166 if self.length is not None:
167 if len(data) > self.length:
167 if len(data) > self.length:
168 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
168 raise AssertionError("Content-length header sent, but more 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 def create_server(ui, repo):
198 def create_server(ui, repo):
199 use_threads = True
199 use_threads = True
200
200
201 def openlog(opt, default):
201 def openlog(opt, default):
202 if opt and opt != '-':
202 if opt and opt != '-':
203 return open(opt, 'w')
203 return open(opt, 'w')
204 return default
204 return default
205
205
206 address = repo.ui.config("web", "address", "")
206 address = repo.ui.config("web", "address", "")
207 port = int(repo.ui.config("web", "port", 8000))
207 port = int(repo.ui.config("web", "port", 8000))
208 use_ipv6 = repo.ui.configbool("web", "ipv6")
208 use_ipv6 = repo.ui.configbool("web", "ipv6")
209 webdir_conf = repo.ui.config("web", "webdir_conf")
209 webdir_conf = repo.ui.config("web", "webdir_conf")
210 ssl_cert = repo.ui.config("web", "certificate")
210 ssl_cert = repo.ui.config("web", "certificate")
211 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
211 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
212 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
212 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
213
213
214 if use_threads:
214 if use_threads:
215 try:
215 try:
216 from threading import activeCount
216 from threading import activeCount
217 except ImportError:
217 except ImportError:
218 use_threads = False
218 use_threads = False
219
219
220 if use_threads:
220 if use_threads:
221 _mixin = SocketServer.ThreadingMixIn
221 _mixin = SocketServer.ThreadingMixIn
222 else:
222 else:
223 if hasattr(os, "fork"):
223 if hasattr(os, "fork"):
224 _mixin = SocketServer.ForkingMixIn
224 _mixin = SocketServer.ForkingMixIn
225 else:
225 else:
226 class _mixin:
226 class _mixin:
227 pass
227 pass
228
228
229 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
229 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
230
230
231 # SO_REUSEADDR has broken semantics on windows
231 # SO_REUSEADDR has broken semantics on windows
232 if os.name == 'nt':
232 if os.name == 'nt':
233 allow_reuse_address = 0
233 allow_reuse_address = 0
234
234
235 def __init__(self, *args, **kargs):
235 def __init__(self, *args, **kargs):
236 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
236 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
237 self.accesslog = accesslog
237 self.accesslog = accesslog
238 self.errorlog = errorlog
238 self.errorlog = errorlog
239 self.daemon_threads = True
239 self.daemon_threads = True
240 def make_handler():
240 def make_handler():
241 if webdir_conf:
241 if webdir_conf:
242 hgwebobj = hgwebdir(webdir_conf, ui)
242 hgwebobj = hgwebdir(webdir_conf, ui)
243 elif repo is not None:
243 elif repo is not None:
244 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
244 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
245 else:
245 else:
246 raise hg.RepoError(_("There is no Mercurial repository here"
246 raise hg.RepoError(_("There is no Mercurial repository here"
247 " (.hg not found)"))
247 " (.hg not found)"))
248 return hgwebobj
248 return hgwebobj
249 self.reqmaker = wsgiapplication(make_handler)
249 self.reqmaker = wsgiapplication(make_handler)
250
250
251 addr = address
251 addr = address
252 if addr in ('', '::'):
252 if addr in ('', '::'):
253 addr = socket.gethostname()
253 addr = socket.gethostname()
254
254
255 self.addr, self.port = addr, port
255 self.addr, self.port = addr, port
256
256
257 if ssl_cert:
257 if ssl_cert:
258 try:
258 try:
259 from OpenSSL import SSL
259 from OpenSSL import SSL
260 ctx = SSL.Context(SSL.SSLv23_METHOD)
260 ctx = SSL.Context(SSL.SSLv23_METHOD)
261 except ImportError:
261 except ImportError:
262 raise util.Abort("SSL support is unavailable")
262 raise util.Abort("SSL support is unavailable")
263 ctx.use_privatekey_file(ssl_cert)
263 ctx.use_privatekey_file(ssl_cert)
264 ctx.use_certificate_file(ssl_cert)
264 ctx.use_certificate_file(ssl_cert)
265 sock = socket.socket(self.address_family, self.socket_type)
265 sock = socket.socket(self.address_family, self.socket_type)
266 self.socket = SSL.Connection(ctx, sock)
266 self.socket = SSL.Connection(ctx, sock)
267 self.server_bind()
267 self.server_bind()
268 self.server_activate()
268 self.server_activate()
269
269
270 class IPv6HTTPServer(MercurialHTTPServer):
270 class IPv6HTTPServer(MercurialHTTPServer):
271 address_family = getattr(socket, 'AF_INET6', None)
271 address_family = getattr(socket, 'AF_INET6', None)
272
272
273 def __init__(self, *args, **kwargs):
273 def __init__(self, *args, **kwargs):
274 if self.address_family is None:
274 if self.address_family is None:
275 raise hg.RepoError(_('IPv6 not available on this system'))
275 raise hg.RepoError(_('IPv6 not available on this system'))
276 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
276 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
277
277
278 if ssl_cert:
278 if ssl_cert:
279 handler = _shgwebhandler
279 handler = _shgwebhandler
280 else:
280 else:
281 handler = _hgwebhandler
281 handler = _hgwebhandler
282
282
283 try:
283 try:
284 if use_ipv6:
284 if use_ipv6:
285 return IPv6HTTPServer((address, port), handler)
285 return IPv6HTTPServer((address, port), handler)
286 else:
286 else:
287 return MercurialHTTPServer((address, port), handler)
287 return MercurialHTTPServer((address, port), handler)
288 except socket.error, inst:
288 except socket.error, inst:
289 raise util.Abort(_('cannot start server: %s') % inst.args[1])
289 raise util.Abort(_('cannot start server: %s') % inst.args[1])
General Comments 0
You need to be logged in to leave comments. Login now