##// END OF EJS Templates
avoid wsgiapplication <-> MercurialHTTPServer circular reference
Alexis S. L. Carvalho -
r4245:bd46b83b default
parent child Browse files
Show More
@@ -1,251 +1,245
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 from mercurial.demandload import demandload
9 from mercurial.demandload import demandload
10 import os, sys, errno
10 import os, sys, errno
11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer traceback")
11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer traceback")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request: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.address_string(),
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.address_string(),
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 if query:
83 if query:
84 env['QUERY_STRING'] = query
84 env['QUERY_STRING'] = query
85 host = self.address_string()
85 host = self.address_string()
86 if host != self.client_address[0]:
86 if host != self.client_address[0]:
87 env['REMOTE_HOST'] = host
87 env['REMOTE_HOST'] = host
88 env['REMOTE_ADDR'] = self.client_address[0]
88 env['REMOTE_ADDR'] = self.client_address[0]
89
89
90 if self.headers.typeheader is None:
90 if self.headers.typeheader is None:
91 env['CONTENT_TYPE'] = self.headers.type
91 env['CONTENT_TYPE'] = self.headers.type
92 else:
92 else:
93 env['CONTENT_TYPE'] = self.headers.typeheader
93 env['CONTENT_TYPE'] = self.headers.typeheader
94 length = self.headers.getheader('content-length')
94 length = self.headers.getheader('content-length')
95 if length:
95 if length:
96 env['CONTENT_LENGTH'] = length
96 env['CONTENT_LENGTH'] = length
97 for header in [h for h in self.headers.keys() \
97 for header in [h for h in self.headers.keys() \
98 if h not in ('content-type', 'content-length')]:
98 if h not in ('content-type', 'content-length')]:
99 hkey = 'HTTP_' + header.replace('-', '_').upper()
99 hkey = 'HTTP_' + header.replace('-', '_').upper()
100 hval = self.headers.getheader(header)
100 hval = self.headers.getheader(header)
101 hval = hval.replace('\n', '').strip()
101 hval = hval.replace('\n', '').strip()
102 if hval:
102 if hval:
103 env[hkey] = hval
103 env[hkey] = hval
104 env['SERVER_PROTOCOL'] = self.request_version
104 env['SERVER_PROTOCOL'] = self.request_version
105 env['wsgi.version'] = (1, 0)
105 env['wsgi.version'] = (1, 0)
106 env['wsgi.url_scheme'] = 'http'
106 env['wsgi.url_scheme'] = 'http'
107 env['wsgi.input'] = self.rfile
107 env['wsgi.input'] = self.rfile
108 env['wsgi.errors'] = _error_logger(self)
108 env['wsgi.errors'] = _error_logger(self)
109 env['wsgi.multithread'] = isinstance(self.server,
109 env['wsgi.multithread'] = isinstance(self.server,
110 SocketServer.ThreadingMixIn)
110 SocketServer.ThreadingMixIn)
111 env['wsgi.multiprocess'] = isinstance(self.server,
111 env['wsgi.multiprocess'] = isinstance(self.server,
112 SocketServer.ForkingMixIn)
112 SocketServer.ForkingMixIn)
113 env['wsgi.run_once'] = 0
113 env['wsgi.run_once'] = 0
114
114
115 self.close_connection = True
115 self.close_connection = True
116 self.saved_status = None
116 self.saved_status = None
117 self.saved_headers = []
117 self.saved_headers = []
118 self.sent_headers = False
118 self.sent_headers = False
119 self.length = None
119 self.length = None
120 req = self.server.reqmaker(env, self._start_response)
120 req = self.server.reqmaker(env, self._start_response)
121 for data in req:
121 for data in req:
122 if data:
122 if data:
123 self._write(data)
123 self._write(data)
124
124
125 def send_headers(self):
125 def send_headers(self):
126 if not self.saved_status:
126 if not self.saved_status:
127 raise AssertionError("Sending headers before start_response() called")
127 raise AssertionError("Sending headers before start_response() called")
128 saved_status = self.saved_status.split(None, 1)
128 saved_status = self.saved_status.split(None, 1)
129 saved_status[0] = int(saved_status[0])
129 saved_status[0] = int(saved_status[0])
130 self.send_response(*saved_status)
130 self.send_response(*saved_status)
131 should_close = True
131 should_close = True
132 for h in self.saved_headers:
132 for h in self.saved_headers:
133 self.send_header(*h)
133 self.send_header(*h)
134 if h[0].lower() == 'content-length':
134 if h[0].lower() == 'content-length':
135 should_close = False
135 should_close = False
136 self.length = int(h[1])
136 self.length = int(h[1])
137 # The value of the Connection header is a list of case-insensitive
137 # The value of the Connection header is a list of case-insensitive
138 # tokens separated by commas and optional whitespace.
138 # tokens separated by commas and optional whitespace.
139 if 'close' in [token.strip().lower() for token in
139 if 'close' in [token.strip().lower() for token in
140 self.headers.get('connection', '').split(',')]:
140 self.headers.get('connection', '').split(',')]:
141 should_close = True
141 should_close = True
142 if should_close:
142 if should_close:
143 self.send_header('Connection', 'close')
143 self.send_header('Connection', 'close')
144 self.close_connection = should_close
144 self.close_connection = should_close
145 self.end_headers()
145 self.end_headers()
146 self.sent_headers = True
146 self.sent_headers = True
147
147
148 def _start_response(self, http_status, headers, exc_info=None):
148 def _start_response(self, http_status, headers, exc_info=None):
149 code, msg = http_status.split(None, 1)
149 code, msg = http_status.split(None, 1)
150 code = int(code)
150 code = int(code)
151 self.saved_status = http_status
151 self.saved_status = http_status
152 bad_headers = ('connection', 'transfer-encoding')
152 bad_headers = ('connection', 'transfer-encoding')
153 self.saved_headers = [ h for h in headers \
153 self.saved_headers = [ h for h in headers \
154 if h[0].lower() not in bad_headers ]
154 if h[0].lower() not in bad_headers ]
155 return self._write
155 return self._write
156
156
157 def _write(self, data):
157 def _write(self, data):
158 if not self.saved_status:
158 if not self.saved_status:
159 raise AssertionError("data written before start_response() called")
159 raise AssertionError("data written before start_response() called")
160 elif not self.sent_headers:
160 elif not self.sent_headers:
161 self.send_headers()
161 self.send_headers()
162 if self.length is not None:
162 if self.length is not None:
163 if len(data) > self.length:
163 if len(data) > self.length:
164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
165 self.length = self.length - len(data)
165 self.length = self.length - len(data)
166 self.wfile.write(data)
166 self.wfile.write(data)
167 self.wfile.flush()
167 self.wfile.flush()
168
168
169 def create_server(ui, repo):
169 def create_server(ui, repo):
170 use_threads = True
170 use_threads = True
171
171
172 def openlog(opt, default):
172 def openlog(opt, default):
173 if opt and opt != '-':
173 if opt and opt != '-':
174 return open(opt, 'w')
174 return open(opt, 'w')
175 return default
175 return default
176
176
177 address = ui.config("web", "address", "")
177 address = ui.config("web", "address", "")
178 port = int(ui.config("web", "port", 8000))
178 port = int(ui.config("web", "port", 8000))
179 use_ipv6 = ui.configbool("web", "ipv6")
179 use_ipv6 = ui.configbool("web", "ipv6")
180 webdir_conf = ui.config("web", "webdir_conf")
180 webdir_conf = ui.config("web", "webdir_conf")
181 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
181 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
182 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
182 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
183
183
184 if use_threads:
184 if use_threads:
185 try:
185 try:
186 from threading import activeCount
186 from threading import activeCount
187 except ImportError:
187 except ImportError:
188 use_threads = False
188 use_threads = False
189
189
190 if use_threads:
190 if use_threads:
191 _mixin = SocketServer.ThreadingMixIn
191 _mixin = SocketServer.ThreadingMixIn
192 else:
192 else:
193 if hasattr(os, "fork"):
193 if hasattr(os, "fork"):
194 _mixin = SocketServer.ForkingMixIn
194 _mixin = SocketServer.ForkingMixIn
195 else:
195 else:
196 class _mixin:
196 class _mixin:
197 pass
197 pass
198
198
199 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
199 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
200
200
201 # SO_REUSEADDR has broken semantics on windows
201 # SO_REUSEADDR has broken semantics on windows
202 if os.name == 'nt':
202 if os.name == 'nt':
203 allow_reuse_address = 0
203 allow_reuse_address = 0
204
204
205 def __init__(self, *args, **kargs):
205 def __init__(self, *args, **kargs):
206 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
206 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
207 self.accesslog = accesslog
207 self.accesslog = accesslog
208 self.errorlog = errorlog
208 self.errorlog = errorlog
209 self.repo = repo
210 self.webdir_conf = webdir_conf
211 self.webdirmaker = hgwebdir
212 self.repoviewmaker = hgweb
213 self.reqmaker = wsgiapplication(self.make_handler)
214 self.daemon_threads = True
209 self.daemon_threads = True
210 def make_handler():
211 if webdir_conf:
212 hgwebobj = hgwebdir(webdir_conf, ui)
213 elif repo is not None:
214 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
215 else:
216 raise hg.RepoError(_("There is no Mercurial repository here"
217 " (.hg not found)"))
218 return hgwebobj
219 self.reqmaker = wsgiapplication(make_handler)
215
220
216 addr, port = self.socket.getsockname()[:2]
221 addr, port = self.socket.getsockname()[:2]
217 if addr in ('0.0.0.0', '::'):
222 if addr in ('0.0.0.0', '::'):
218 addr = socket.gethostname()
223 addr = socket.gethostname()
219 else:
224 else:
220 try:
225 try:
221 addr = socket.gethostbyaddr(addr)[0]
226 addr = socket.gethostbyaddr(addr)[0]
222 except socket.error:
227 except socket.error:
223 pass
228 pass
224 self.addr, self.port = addr, port
229 self.addr, self.port = addr, port
225
230
226 def make_handler(self):
227 if self.webdir_conf:
228 hgwebobj = self.webdirmaker(self.webdir_conf, ui)
229 elif self.repo is not None:
230 hgwebobj = self.repoviewmaker(hg.repository(repo.ui,
231 repo.root))
232 else:
233 raise hg.RepoError(_("There is no Mercurial repository here"
234 " (.hg not found)"))
235 return hgwebobj
236
237 class IPv6HTTPServer(MercurialHTTPServer):
231 class IPv6HTTPServer(MercurialHTTPServer):
238 address_family = getattr(socket, 'AF_INET6', None)
232 address_family = getattr(socket, 'AF_INET6', None)
239
233
240 def __init__(self, *args, **kwargs):
234 def __init__(self, *args, **kwargs):
241 if self.address_family is None:
235 if self.address_family is None:
242 raise hg.RepoError(_('IPv6 not available on this system'))
236 raise hg.RepoError(_('IPv6 not available on this system'))
243 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
237 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
244
238
245 try:
239 try:
246 if use_ipv6:
240 if use_ipv6:
247 return IPv6HTTPServer((address, port), _hgwebhandler)
241 return IPv6HTTPServer((address, port), _hgwebhandler)
248 else:
242 else:
249 return MercurialHTTPServer((address, port), _hgwebhandler)
243 return MercurialHTTPServer((address, port), _hgwebhandler)
250 except socket.error, inst:
244 except socket.error, inst:
251 raise util.Abort(_('cannot start server: %s') % inst.args[1])
245 raise util.Abort(_('cannot start server: %s') % inst.args[1])
General Comments 0
You need to be logged in to leave comments. Login now