##// 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 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, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.demandload import demandload
10 10 import os, sys, errno
11 11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer traceback")
12 12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
14 14 from mercurial.i18n import gettext as _
15 15
16 16 def _splitURI(uri):
17 17 """ Return path and query splited from uri
18 18
19 19 Just like CGI environment, the path is unquoted, the query is
20 20 not.
21 21 """
22 22 if '?' in uri:
23 23 path, query = uri.split('?', 1)
24 24 else:
25 25 path, query = uri, ''
26 26 return urllib.unquote(path), query
27 27
28 28 class _error_logger(object):
29 29 def __init__(self, handler):
30 30 self.handler = handler
31 31 def flush(self):
32 32 pass
33 33 def write(self, str):
34 34 self.writelines(str.split('\n'))
35 35 def writelines(self, seq):
36 36 for msg in seq:
37 37 self.handler.log_error("HG error: %s", msg)
38 38
39 39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
40 40 def __init__(self, *args, **kargs):
41 41 self.protocol_version = 'HTTP/1.1'
42 42 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
43 43
44 44 def log_error(self, format, *args):
45 45 errorlog = self.server.errorlog
46 46 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
47 47 self.log_date_time_string(),
48 48 format % args))
49 49
50 50 def log_message(self, format, *args):
51 51 accesslog = self.server.accesslog
52 52 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
53 53 self.log_date_time_string(),
54 54 format % args))
55 55
56 56 def do_POST(self):
57 57 try:
58 58 try:
59 59 self.do_hgweb()
60 60 except socket.error, inst:
61 61 if inst[0] != errno.EPIPE:
62 62 raise
63 63 except StandardError, inst:
64 64 self._start_response("500 Internal Server Error", [])
65 65 self._write("Internal Server Error")
66 66 tb = "".join(traceback.format_exception(*sys.exc_info()))
67 67 self.log_error("Exception happened during processing request '%s':\n%s",
68 68 self.path, tb)
69 69
70 70 def do_GET(self):
71 71 self.do_POST()
72 72
73 73 def do_hgweb(self):
74 74 path_info, query = _splitURI(self.path)
75 75
76 76 env = {}
77 77 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
78 78 env['REQUEST_METHOD'] = self.command
79 79 env['SERVER_NAME'] = self.server.server_name
80 80 env['SERVER_PORT'] = str(self.server.server_port)
81 81 env['REQUEST_URI'] = self.path
82 82 env['PATH_INFO'] = path_info
83 83 if query:
84 84 env['QUERY_STRING'] = query
85 85 host = self.address_string()
86 86 if host != self.client_address[0]:
87 87 env['REMOTE_HOST'] = host
88 88 env['REMOTE_ADDR'] = self.client_address[0]
89 89
90 90 if self.headers.typeheader is None:
91 91 env['CONTENT_TYPE'] = self.headers.type
92 92 else:
93 93 env['CONTENT_TYPE'] = self.headers.typeheader
94 94 length = self.headers.getheader('content-length')
95 95 if length:
96 96 env['CONTENT_LENGTH'] = length
97 97 for header in [h for h in self.headers.keys() \
98 98 if h not in ('content-type', 'content-length')]:
99 99 hkey = 'HTTP_' + header.replace('-', '_').upper()
100 100 hval = self.headers.getheader(header)
101 101 hval = hval.replace('\n', '').strip()
102 102 if hval:
103 103 env[hkey] = hval
104 104 env['SERVER_PROTOCOL'] = self.request_version
105 105 env['wsgi.version'] = (1, 0)
106 106 env['wsgi.url_scheme'] = 'http'
107 107 env['wsgi.input'] = self.rfile
108 108 env['wsgi.errors'] = _error_logger(self)
109 109 env['wsgi.multithread'] = isinstance(self.server,
110 110 SocketServer.ThreadingMixIn)
111 111 env['wsgi.multiprocess'] = isinstance(self.server,
112 112 SocketServer.ForkingMixIn)
113 113 env['wsgi.run_once'] = 0
114 114
115 115 self.close_connection = True
116 116 self.saved_status = None
117 117 self.saved_headers = []
118 118 self.sent_headers = False
119 119 self.length = None
120 120 req = self.server.reqmaker(env, self._start_response)
121 121 for data in req:
122 122 if data:
123 123 self._write(data)
124 124
125 125 def send_headers(self):
126 126 if not self.saved_status:
127 127 raise AssertionError("Sending headers before start_response() called")
128 128 saved_status = self.saved_status.split(None, 1)
129 129 saved_status[0] = int(saved_status[0])
130 130 self.send_response(*saved_status)
131 131 should_close = True
132 132 for h in self.saved_headers:
133 133 self.send_header(*h)
134 134 if h[0].lower() == 'content-length':
135 135 should_close = False
136 136 self.length = int(h[1])
137 137 # The value of the Connection header is a list of case-insensitive
138 138 # tokens separated by commas and optional whitespace.
139 139 if 'close' in [token.strip().lower() for token in
140 140 self.headers.get('connection', '').split(',')]:
141 141 should_close = True
142 142 if should_close:
143 143 self.send_header('Connection', 'close')
144 144 self.close_connection = should_close
145 145 self.end_headers()
146 146 self.sent_headers = True
147 147
148 148 def _start_response(self, http_status, headers, exc_info=None):
149 149 code, msg = http_status.split(None, 1)
150 150 code = int(code)
151 151 self.saved_status = http_status
152 152 bad_headers = ('connection', 'transfer-encoding')
153 153 self.saved_headers = [ h for h in headers \
154 154 if h[0].lower() not in bad_headers ]
155 155 return self._write
156 156
157 157 def _write(self, data):
158 158 if not self.saved_status:
159 159 raise AssertionError("data written before start_response() called")
160 160 elif not self.sent_headers:
161 161 self.send_headers()
162 162 if self.length is not None:
163 163 if len(data) > self.length:
164 164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
165 165 self.length = self.length - len(data)
166 166 self.wfile.write(data)
167 167 self.wfile.flush()
168 168
169 169 def create_server(ui, repo):
170 170 use_threads = True
171 171
172 172 def openlog(opt, default):
173 173 if opt and opt != '-':
174 174 return open(opt, 'w')
175 175 return default
176 176
177 177 address = ui.config("web", "address", "")
178 178 port = int(ui.config("web", "port", 8000))
179 179 use_ipv6 = ui.configbool("web", "ipv6")
180 180 webdir_conf = ui.config("web", "webdir_conf")
181 181 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
182 182 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
183 183
184 184 if use_threads:
185 185 try:
186 186 from threading import activeCount
187 187 except ImportError:
188 188 use_threads = False
189 189
190 190 if use_threads:
191 191 _mixin = SocketServer.ThreadingMixIn
192 192 else:
193 193 if hasattr(os, "fork"):
194 194 _mixin = SocketServer.ForkingMixIn
195 195 else:
196 196 class _mixin:
197 197 pass
198 198
199 199 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
200 200
201 201 # SO_REUSEADDR has broken semantics on windows
202 202 if os.name == 'nt':
203 203 allow_reuse_address = 0
204 204
205 205 def __init__(self, *args, **kargs):
206 206 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
207 207 self.accesslog = accesslog
208 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 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 221 addr, port = self.socket.getsockname()[:2]
217 222 if addr in ('0.0.0.0', '::'):
218 223 addr = socket.gethostname()
219 224 else:
220 225 try:
221 226 addr = socket.gethostbyaddr(addr)[0]
222 227 except socket.error:
223 228 pass
224 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 231 class IPv6HTTPServer(MercurialHTTPServer):
238 232 address_family = getattr(socket, 'AF_INET6', None)
239 233
240 234 def __init__(self, *args, **kwargs):
241 235 if self.address_family is None:
242 236 raise hg.RepoError(_('IPv6 not available on this system'))
243 237 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
244 238
245 239 try:
246 240 if use_ipv6:
247 241 return IPv6HTTPServer((address, port), _hgwebhandler)
248 242 else:
249 243 return MercurialHTTPServer((address, port), _hgwebhandler)
250 244 except socket.error, inst:
251 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