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