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