##// END OF EJS Templates
Make hgweb threads into daemon threads....
Brendan Cully -
r2650:56e98084 default
parent child Browse files
Show More
@@ -1,223 +1,224 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 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 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")
11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer")
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(str):
33 def write(str):
34 self.writelines(str.split('\n'))
34 self.writelines(str.split('\n'))
35 def writelines(seq):
35 def writelines(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 self.do_hgweb()
58 self.do_hgweb()
59 except socket.error, inst:
59 except socket.error, inst:
60 if inst[0] != errno.EPIPE:
60 if inst[0] != errno.EPIPE:
61 raise
61 raise
62
62
63 def do_GET(self):
63 def do_GET(self):
64 self.do_POST()
64 self.do_POST()
65
65
66 def do_hgweb(self):
66 def do_hgweb(self):
67 path_info, query = _splitURI(self.path)
67 path_info, query = _splitURI(self.path)
68
68
69 env = {}
69 env = {}
70 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
70 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
71 env['REQUEST_METHOD'] = self.command
71 env['REQUEST_METHOD'] = self.command
72 env['SERVER_NAME'] = self.server.server_name
72 env['SERVER_NAME'] = self.server.server_name
73 env['SERVER_PORT'] = str(self.server.server_port)
73 env['SERVER_PORT'] = str(self.server.server_port)
74 env['REQUEST_URI'] = "/"
74 env['REQUEST_URI'] = "/"
75 env['PATH_INFO'] = path_info
75 env['PATH_INFO'] = path_info
76 if query:
76 if query:
77 env['QUERY_STRING'] = query
77 env['QUERY_STRING'] = query
78 host = self.address_string()
78 host = self.address_string()
79 if host != self.client_address[0]:
79 if host != self.client_address[0]:
80 env['REMOTE_HOST'] = host
80 env['REMOTE_HOST'] = host
81 env['REMOTE_ADDR'] = self.client_address[0]
81 env['REMOTE_ADDR'] = self.client_address[0]
82
82
83 if self.headers.typeheader is None:
83 if self.headers.typeheader is None:
84 env['CONTENT_TYPE'] = self.headers.type
84 env['CONTENT_TYPE'] = self.headers.type
85 else:
85 else:
86 env['CONTENT_TYPE'] = self.headers.typeheader
86 env['CONTENT_TYPE'] = self.headers.typeheader
87 length = self.headers.getheader('content-length')
87 length = self.headers.getheader('content-length')
88 if length:
88 if length:
89 env['CONTENT_LENGTH'] = length
89 env['CONTENT_LENGTH'] = length
90 for header in [h for h in self.headers.keys() \
90 for header in [h for h in self.headers.keys() \
91 if h not in ('content-type', 'content-length')]:
91 if h not in ('content-type', 'content-length')]:
92 hkey = 'HTTP_' + header.replace('-', '_').upper()
92 hkey = 'HTTP_' + header.replace('-', '_').upper()
93 hval = self.headers.getheader(header)
93 hval = self.headers.getheader(header)
94 hval = hval.replace('\n', '').strip()
94 hval = hval.replace('\n', '').strip()
95 if hval:
95 if hval:
96 env[hkey] = hval
96 env[hkey] = hval
97 env['SERVER_PROTOCOL'] = self.request_version
97 env['SERVER_PROTOCOL'] = self.request_version
98 env['wsgi.version'] = (1, 0)
98 env['wsgi.version'] = (1, 0)
99 env['wsgi.url_scheme'] = 'http'
99 env['wsgi.url_scheme'] = 'http'
100 env['wsgi.input'] = self.rfile
100 env['wsgi.input'] = self.rfile
101 env['wsgi.errors'] = _error_logger(self)
101 env['wsgi.errors'] = _error_logger(self)
102 env['wsgi.multithread'] = isinstance(self.server,
102 env['wsgi.multithread'] = isinstance(self.server,
103 SocketServer.ThreadingMixIn)
103 SocketServer.ThreadingMixIn)
104 env['wsgi.multiprocess'] = isinstance(self.server,
104 env['wsgi.multiprocess'] = isinstance(self.server,
105 SocketServer.ForkingMixIn)
105 SocketServer.ForkingMixIn)
106 env['wsgi.run_once'] = 0
106 env['wsgi.run_once'] = 0
107
107
108 self.close_connection = True
108 self.close_connection = True
109 self.saved_status = None
109 self.saved_status = None
110 self.saved_headers = []
110 self.saved_headers = []
111 self.sent_headers = False
111 self.sent_headers = False
112 self.length = None
112 self.length = None
113 req = self.server.reqmaker(env, self._start_response)
113 req = self.server.reqmaker(env, self._start_response)
114 for data in req:
114 for data in req:
115 if data:
115 if data:
116 self._write(data)
116 self._write(data)
117
117
118 def send_headers(self):
118 def send_headers(self):
119 if not self.saved_status:
119 if not self.saved_status:
120 raise AssertionError("Sending headers before start_response() called")
120 raise AssertionError("Sending headers before start_response() called")
121 saved_status = self.saved_status.split(None, 1)
121 saved_status = self.saved_status.split(None, 1)
122 saved_status[0] = int(saved_status[0])
122 saved_status[0] = int(saved_status[0])
123 self.send_response(*saved_status)
123 self.send_response(*saved_status)
124 should_close = True
124 should_close = True
125 for h in self.saved_headers:
125 for h in self.saved_headers:
126 self.send_header(*h)
126 self.send_header(*h)
127 if h[0].lower() == 'content-length':
127 if h[0].lower() == 'content-length':
128 should_close = False
128 should_close = False
129 self.length = int(h[1])
129 self.length = int(h[1])
130 # The value of the Connection header is a list of case-insensitive
130 # The value of the Connection header is a list of case-insensitive
131 # tokens separated by commas and optional whitespace.
131 # tokens separated by commas and optional whitespace.
132 if 'close' in [token.strip().lower() for token in
132 if 'close' in [token.strip().lower() for token in
133 self.headers.get('connection', '').split(',')]:
133 self.headers.get('connection', '').split(',')]:
134 should_close = True
134 should_close = True
135 if should_close:
135 if should_close:
136 self.send_header('Connection', 'close')
136 self.send_header('Connection', 'close')
137 self.close_connection = should_close
137 self.close_connection = should_close
138 self.end_headers()
138 self.end_headers()
139 self.sent_headers = True
139 self.sent_headers = True
140
140
141 def _start_response(self, http_status, headers, exc_info=None):
141 def _start_response(self, http_status, headers, exc_info=None):
142 code, msg = http_status.split(None, 1)
142 code, msg = http_status.split(None, 1)
143 code = int(code)
143 code = int(code)
144 self.saved_status = http_status
144 self.saved_status = http_status
145 bad_headers = ('connection', 'transfer-encoding')
145 bad_headers = ('connection', 'transfer-encoding')
146 self.saved_headers = [ h for h in headers \
146 self.saved_headers = [ h for h in headers \
147 if h[0].lower() not in bad_headers ]
147 if h[0].lower() not in bad_headers ]
148 return self._write
148 return self._write
149
149
150 def _write(self, data):
150 def _write(self, data):
151 if not self.saved_status:
151 if not self.saved_status:
152 raise AssertionError("data written before start_response() called")
152 raise AssertionError("data written before start_response() called")
153 elif not self.sent_headers:
153 elif not self.sent_headers:
154 self.send_headers()
154 self.send_headers()
155 if self.length is not None:
155 if self.length is not None:
156 if len(data) > self.length:
156 if len(data) > self.length:
157 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
157 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
158 self.length = self.length - len(data)
158 self.length = self.length - len(data)
159 self.wfile.write(data)
159 self.wfile.write(data)
160 self.wfile.flush()
160 self.wfile.flush()
161
161
162 def create_server(ui, repo):
162 def create_server(ui, repo):
163 use_threads = True
163 use_threads = True
164
164
165 def openlog(opt, default):
165 def openlog(opt, default):
166 if opt and opt != '-':
166 if opt and opt != '-':
167 return open(opt, 'w')
167 return open(opt, 'w')
168 return default
168 return default
169
169
170 address = ui.config("web", "address", "")
170 address = ui.config("web", "address", "")
171 port = int(ui.config("web", "port", 8000))
171 port = int(ui.config("web", "port", 8000))
172 use_ipv6 = ui.configbool("web", "ipv6")
172 use_ipv6 = ui.configbool("web", "ipv6")
173 webdir_conf = ui.config("web", "webdir_conf")
173 webdir_conf = ui.config("web", "webdir_conf")
174 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
174 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
175 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
175 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
176
176
177 if use_threads:
177 if use_threads:
178 try:
178 try:
179 from threading import activeCount
179 from threading import activeCount
180 except ImportError:
180 except ImportError:
181 use_threads = False
181 use_threads = False
182
182
183 if use_threads:
183 if use_threads:
184 _mixin = SocketServer.ThreadingMixIn
184 _mixin = SocketServer.ThreadingMixIn
185 else:
185 else:
186 if hasattr(os, "fork"):
186 if hasattr(os, "fork"):
187 _mixin = SocketServer.ForkingMixIn
187 _mixin = SocketServer.ForkingMixIn
188 else:
188 else:
189 class _mixin: pass
189 class _mixin: pass
190
190
191 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
191 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
192 def __init__(self, *args, **kargs):
192 def __init__(self, *args, **kargs):
193 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
193 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
194 self.accesslog = accesslog
194 self.accesslog = accesslog
195 self.errorlog = errorlog
195 self.errorlog = errorlog
196 self.repo = repo
196 self.repo = repo
197 self.webdir_conf = webdir_conf
197 self.webdir_conf = webdir_conf
198 self.webdirmaker = hgwebdir
198 self.webdirmaker = hgwebdir
199 self.repoviewmaker = hgweb
199 self.repoviewmaker = hgweb
200 self.reqmaker = wsgiapplication(self.make_handler)
200 self.reqmaker = wsgiapplication(self.make_handler)
201 self.daemon_threads = True
201
202
202 def make_handler(self):
203 def make_handler(self):
203 if self.webdir_conf:
204 if self.webdir_conf:
204 hgwebobj = self.webdirmaker(self.webdir_conf)
205 hgwebobj = self.webdirmaker(self.webdir_conf)
205 elif self.repo is not None:
206 elif self.repo is not None:
206 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
207 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
207 repo.origroot))
208 repo.origroot))
208 else:
209 else:
209 raise hg.RepoError(_('no repo found'))
210 raise hg.RepoError(_('no repo found'))
210 return hgwebobj
211 return hgwebobj
211
212
212 class IPv6HTTPServer(MercurialHTTPServer):
213 class IPv6HTTPServer(MercurialHTTPServer):
213 address_family = getattr(socket, 'AF_INET6', None)
214 address_family = getattr(socket, 'AF_INET6', None)
214
215
215 def __init__(self, *args, **kwargs):
216 def __init__(self, *args, **kwargs):
216 if self.address_family is None:
217 if self.address_family is None:
217 raise hg.RepoError(_('IPv6 not available on this system'))
218 raise hg.RepoError(_('IPv6 not available on this system'))
218 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
219 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
219
220
220 if use_ipv6:
221 if use_ipv6:
221 return IPv6HTTPServer((address, port), _hgwebhandler)
222 return IPv6HTTPServer((address, port), _hgwebhandler)
222 else:
223 else:
223 return MercurialHTTPServer((address, port), _hgwebhandler)
224 return MercurialHTTPServer((address, port), _hgwebhandler)
General Comments 0
You need to be logged in to leave comments. Login now