##// END OF EJS Templates
Merge with stable
Matt Mackall -
r4379:80c7fa62 merge default
parent child Browse files
Show More
@@ -1,245 +1,243 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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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 def __init__(self, *args, **kargs):
40 def __init__(self, *args, **kargs):
41 self.protocol_version = 'HTTP/1.1'
41 self.protocol_version = 'HTTP/1.1'
42 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
42 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
43
43
44 def log_error(self, format, *args):
44 def log_error(self, format, *args):
45 errorlog = self.server.errorlog
45 errorlog = self.server.errorlog
46 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
46 errorlog.write("%s - - [%s] %s\n" % (self.client_address[0],
47 self.log_date_time_string(),
47 self.log_date_time_string(),
48 format % args))
48 format % args))
49
49
50 def log_message(self, format, *args):
50 def log_message(self, format, *args):
51 accesslog = self.server.accesslog
51 accesslog = self.server.accesslog
52 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
52 accesslog.write("%s - - [%s] %s\n" % (self.client_address[0],
53 self.log_date_time_string(),
53 self.log_date_time_string(),
54 format % args))
54 format % args))
55
55
56 def do_POST(self):
56 def do_POST(self):
57 try:
57 try:
58 try:
58 try:
59 self.do_hgweb()
59 self.do_hgweb()
60 except socket.error, inst:
60 except socket.error, inst:
61 if inst[0] != errno.EPIPE:
61 if inst[0] != errno.EPIPE:
62 raise
62 raise
63 except StandardError, inst:
63 except StandardError, inst:
64 self._start_response("500 Internal Server Error", [])
64 self._start_response("500 Internal Server Error", [])
65 self._write("Internal Server Error")
65 self._write("Internal Server Error")
66 tb = "".join(traceback.format_exception(*sys.exc_info()))
66 tb = "".join(traceback.format_exception(*sys.exc_info()))
67 self.log_error("Exception happened during processing request '%s':\n%s",
67 self.log_error("Exception happened during processing request '%s':\n%s",
68 self.path, tb)
68 self.path, tb)
69
69
70 def do_GET(self):
70 def do_GET(self):
71 self.do_POST()
71 self.do_POST()
72
72
73 def do_hgweb(self):
73 def do_hgweb(self):
74 path_info, query = _splitURI(self.path)
74 path_info, query = _splitURI(self.path)
75
75
76 env = {}
76 env = {}
77 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
77 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
78 env['REQUEST_METHOD'] = self.command
78 env['REQUEST_METHOD'] = self.command
79 env['SERVER_NAME'] = self.server.server_name
79 env['SERVER_NAME'] = self.server.server_name
80 env['SERVER_PORT'] = str(self.server.server_port)
80 env['SERVER_PORT'] = str(self.server.server_port)
81 env['REQUEST_URI'] = self.path
81 env['REQUEST_URI'] = self.path
82 env['PATH_INFO'] = path_info
82 env['PATH_INFO'] = path_info
83 env['REMOTE_HOST'] = self.client_address[0]
84 env['REMOTE_ADDR'] = self.client_address[0]
83 if query:
85 if query:
84 env['QUERY_STRING'] = query
86 env['QUERY_STRING'] = query
85 host = self.address_string()
86 if host != self.client_address[0]:
87 env['REMOTE_HOST'] = host
88 env['REMOTE_ADDR'] = self.client_address[0]
89
87
90 if self.headers.typeheader is None:
88 if self.headers.typeheader is None:
91 env['CONTENT_TYPE'] = self.headers.type
89 env['CONTENT_TYPE'] = self.headers.type
92 else:
90 else:
93 env['CONTENT_TYPE'] = self.headers.typeheader
91 env['CONTENT_TYPE'] = self.headers.typeheader
94 length = self.headers.getheader('content-length')
92 length = self.headers.getheader('content-length')
95 if length:
93 if length:
96 env['CONTENT_LENGTH'] = length
94 env['CONTENT_LENGTH'] = length
97 for header in [h for h in self.headers.keys() \
95 for header in [h for h in self.headers.keys() \
98 if h not in ('content-type', 'content-length')]:
96 if h not in ('content-type', 'content-length')]:
99 hkey = 'HTTP_' + header.replace('-', '_').upper()
97 hkey = 'HTTP_' + header.replace('-', '_').upper()
100 hval = self.headers.getheader(header)
98 hval = self.headers.getheader(header)
101 hval = hval.replace('\n', '').strip()
99 hval = hval.replace('\n', '').strip()
102 if hval:
100 if hval:
103 env[hkey] = hval
101 env[hkey] = hval
104 env['SERVER_PROTOCOL'] = self.request_version
102 env['SERVER_PROTOCOL'] = self.request_version
105 env['wsgi.version'] = (1, 0)
103 env['wsgi.version'] = (1, 0)
106 env['wsgi.url_scheme'] = 'http'
104 env['wsgi.url_scheme'] = 'http'
107 env['wsgi.input'] = self.rfile
105 env['wsgi.input'] = self.rfile
108 env['wsgi.errors'] = _error_logger(self)
106 env['wsgi.errors'] = _error_logger(self)
109 env['wsgi.multithread'] = isinstance(self.server,
107 env['wsgi.multithread'] = isinstance(self.server,
110 SocketServer.ThreadingMixIn)
108 SocketServer.ThreadingMixIn)
111 env['wsgi.multiprocess'] = isinstance(self.server,
109 env['wsgi.multiprocess'] = isinstance(self.server,
112 SocketServer.ForkingMixIn)
110 SocketServer.ForkingMixIn)
113 env['wsgi.run_once'] = 0
111 env['wsgi.run_once'] = 0
114
112
115 self.close_connection = True
113 self.close_connection = True
116 self.saved_status = None
114 self.saved_status = None
117 self.saved_headers = []
115 self.saved_headers = []
118 self.sent_headers = False
116 self.sent_headers = False
119 self.length = None
117 self.length = None
120 req = self.server.reqmaker(env, self._start_response)
118 req = self.server.reqmaker(env, self._start_response)
121 for data in req:
119 for data in req:
122 if data:
120 if data:
123 self._write(data)
121 self._write(data)
124
122
125 def send_headers(self):
123 def send_headers(self):
126 if not self.saved_status:
124 if not self.saved_status:
127 raise AssertionError("Sending headers before start_response() called")
125 raise AssertionError("Sending headers before start_response() called")
128 saved_status = self.saved_status.split(None, 1)
126 saved_status = self.saved_status.split(None, 1)
129 saved_status[0] = int(saved_status[0])
127 saved_status[0] = int(saved_status[0])
130 self.send_response(*saved_status)
128 self.send_response(*saved_status)
131 should_close = True
129 should_close = True
132 for h in self.saved_headers:
130 for h in self.saved_headers:
133 self.send_header(*h)
131 self.send_header(*h)
134 if h[0].lower() == 'content-length':
132 if h[0].lower() == 'content-length':
135 should_close = False
133 should_close = False
136 self.length = int(h[1])
134 self.length = int(h[1])
137 # The value of the Connection header is a list of case-insensitive
135 # The value of the Connection header is a list of case-insensitive
138 # tokens separated by commas and optional whitespace.
136 # tokens separated by commas and optional whitespace.
139 if 'close' in [token.strip().lower() for token in
137 if 'close' in [token.strip().lower() for token in
140 self.headers.get('connection', '').split(',')]:
138 self.headers.get('connection', '').split(',')]:
141 should_close = True
139 should_close = True
142 if should_close:
140 if should_close:
143 self.send_header('Connection', 'close')
141 self.send_header('Connection', 'close')
144 self.close_connection = should_close
142 self.close_connection = should_close
145 self.end_headers()
143 self.end_headers()
146 self.sent_headers = True
144 self.sent_headers = True
147
145
148 def _start_response(self, http_status, headers, exc_info=None):
146 def _start_response(self, http_status, headers, exc_info=None):
149 code, msg = http_status.split(None, 1)
147 code, msg = http_status.split(None, 1)
150 code = int(code)
148 code = int(code)
151 self.saved_status = http_status
149 self.saved_status = http_status
152 bad_headers = ('connection', 'transfer-encoding')
150 bad_headers = ('connection', 'transfer-encoding')
153 self.saved_headers = [ h for h in headers \
151 self.saved_headers = [ h for h in headers \
154 if h[0].lower() not in bad_headers ]
152 if h[0].lower() not in bad_headers ]
155 return self._write
153 return self._write
156
154
157 def _write(self, data):
155 def _write(self, data):
158 if not self.saved_status:
156 if not self.saved_status:
159 raise AssertionError("data written before start_response() called")
157 raise AssertionError("data written before start_response() called")
160 elif not self.sent_headers:
158 elif not self.sent_headers:
161 self.send_headers()
159 self.send_headers()
162 if self.length is not None:
160 if self.length is not None:
163 if len(data) > self.length:
161 if len(data) > self.length:
164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
162 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
165 self.length = self.length - len(data)
163 self.length = self.length - len(data)
166 self.wfile.write(data)
164 self.wfile.write(data)
167 self.wfile.flush()
165 self.wfile.flush()
168
166
169 def create_server(ui, repo):
167 def create_server(ui, repo):
170 use_threads = True
168 use_threads = True
171
169
172 def openlog(opt, default):
170 def openlog(opt, default):
173 if opt and opt != '-':
171 if opt and opt != '-':
174 return open(opt, 'w')
172 return open(opt, 'w')
175 return default
173 return default
176
174
177 address = ui.config("web", "address", "")
175 address = ui.config("web", "address", "")
178 port = int(ui.config("web", "port", 8000))
176 port = int(ui.config("web", "port", 8000))
179 use_ipv6 = ui.configbool("web", "ipv6")
177 use_ipv6 = ui.configbool("web", "ipv6")
180 webdir_conf = ui.config("web", "webdir_conf")
178 webdir_conf = ui.config("web", "webdir_conf")
181 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
179 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
182 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
180 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
183
181
184 if use_threads:
182 if use_threads:
185 try:
183 try:
186 from threading import activeCount
184 from threading import activeCount
187 except ImportError:
185 except ImportError:
188 use_threads = False
186 use_threads = False
189
187
190 if use_threads:
188 if use_threads:
191 _mixin = SocketServer.ThreadingMixIn
189 _mixin = SocketServer.ThreadingMixIn
192 else:
190 else:
193 if hasattr(os, "fork"):
191 if hasattr(os, "fork"):
194 _mixin = SocketServer.ForkingMixIn
192 _mixin = SocketServer.ForkingMixIn
195 else:
193 else:
196 class _mixin:
194 class _mixin:
197 pass
195 pass
198
196
199 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
197 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
200
198
201 # SO_REUSEADDR has broken semantics on windows
199 # SO_REUSEADDR has broken semantics on windows
202 if os.name == 'nt':
200 if os.name == 'nt':
203 allow_reuse_address = 0
201 allow_reuse_address = 0
204
202
205 def __init__(self, *args, **kargs):
203 def __init__(self, *args, **kargs):
206 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
204 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
207 self.accesslog = accesslog
205 self.accesslog = accesslog
208 self.errorlog = errorlog
206 self.errorlog = errorlog
209 self.daemon_threads = True
207 self.daemon_threads = True
210 def make_handler():
208 def make_handler():
211 if webdir_conf:
209 if webdir_conf:
212 hgwebobj = hgwebdir(webdir_conf, ui)
210 hgwebobj = hgwebdir(webdir_conf, ui)
213 elif repo is not None:
211 elif repo is not None:
214 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
212 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
215 else:
213 else:
216 raise hg.RepoError(_("There is no Mercurial repository here"
214 raise hg.RepoError(_("There is no Mercurial repository here"
217 " (.hg not found)"))
215 " (.hg not found)"))
218 return hgwebobj
216 return hgwebobj
219 self.reqmaker = wsgiapplication(make_handler)
217 self.reqmaker = wsgiapplication(make_handler)
220
218
221 addr, port = self.socket.getsockname()[:2]
219 addr, port = self.socket.getsockname()[:2]
222 if addr in ('0.0.0.0', '::'):
220 if addr in ('0.0.0.0', '::'):
223 addr = socket.gethostname()
221 addr = socket.gethostname()
224 else:
222 else:
225 try:
223 try:
226 addr = socket.gethostbyaddr(addr)[0]
224 addr = socket.gethostbyaddr(addr)[0]
227 except socket.error:
225 except socket.error:
228 pass
226 pass
229 self.addr, self.port = addr, port
227 self.addr, self.port = addr, port
230
228
231 class IPv6HTTPServer(MercurialHTTPServer):
229 class IPv6HTTPServer(MercurialHTTPServer):
232 address_family = getattr(socket, 'AF_INET6', None)
230 address_family = getattr(socket, 'AF_INET6', None)
233
231
234 def __init__(self, *args, **kwargs):
232 def __init__(self, *args, **kwargs):
235 if self.address_family is None:
233 if self.address_family is None:
236 raise hg.RepoError(_('IPv6 not available on this system'))
234 raise hg.RepoError(_('IPv6 not available on this system'))
237 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
235 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
238
236
239 try:
237 try:
240 if use_ipv6:
238 if use_ipv6:
241 return IPv6HTTPServer((address, port), _hgwebhandler)
239 return IPv6HTTPServer((address, port), _hgwebhandler)
242 else:
240 else:
243 return MercurialHTTPServer((address, port), _hgwebhandler)
241 return MercurialHTTPServer((address, port), _hgwebhandler)
244 except socket.error, inst:
242 except socket.error, inst:
245 raise util.Abort(_('cannot start server: %s') % inst.args[1])
243 raise util.Abort(_('cannot start server: %s') % inst.args[1])
General Comments 0
You need to be logged in to leave comments. Login now