##// END OF EJS Templates
Respect "Connection: close" headers sent by HTTP clients....
Alexis S. L. Carvalho -
r2582:276de216 default
parent child Browse files
Show More
@@ -1,218 +1,223 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
131 # tokens separated by commas and optional whitespace.
132 if 'close' in [token.strip().lower() for token in
133 self.headers.get('connection', '').split(',')]:
134 should_close = True
130 if should_close:
135 if should_close:
131 self.send_header('Connection', 'close')
136 self.send_header('Connection', 'close')
132 self.close_connection = should_close
137 self.close_connection = should_close
133 self.end_headers()
138 self.end_headers()
134 self.sent_headers = True
139 self.sent_headers = True
135
140
136 def _start_response(self, http_status, headers, exc_info=None):
141 def _start_response(self, http_status, headers, exc_info=None):
137 code, msg = http_status.split(None, 1)
142 code, msg = http_status.split(None, 1)
138 code = int(code)
143 code = int(code)
139 self.saved_status = http_status
144 self.saved_status = http_status
140 bad_headers = ('connection', 'transfer-encoding')
145 bad_headers = ('connection', 'transfer-encoding')
141 self.saved_headers = [ h for h in headers \
146 self.saved_headers = [ h for h in headers \
142 if h[0].lower() not in bad_headers ]
147 if h[0].lower() not in bad_headers ]
143 return self._write
148 return self._write
144
149
145 def _write(self, data):
150 def _write(self, data):
146 if not self.saved_status:
151 if not self.saved_status:
147 raise AssertionError("data written before start_response() called")
152 raise AssertionError("data written before start_response() called")
148 elif not self.sent_headers:
153 elif not self.sent_headers:
149 self.send_headers()
154 self.send_headers()
150 if self.length is not None:
155 if self.length is not None:
151 if len(data) > self.length:
156 if len(data) > self.length:
152 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.")
153 self.length = self.length - len(data)
158 self.length = self.length - len(data)
154 self.wfile.write(data)
159 self.wfile.write(data)
155 self.wfile.flush()
160 self.wfile.flush()
156
161
157 def create_server(ui, repo):
162 def create_server(ui, repo):
158 use_threads = True
163 use_threads = True
159
164
160 def openlog(opt, default):
165 def openlog(opt, default):
161 if opt and opt != '-':
166 if opt and opt != '-':
162 return open(opt, 'w')
167 return open(opt, 'w')
163 return default
168 return default
164
169
165 address = ui.config("web", "address", "")
170 address = ui.config("web", "address", "")
166 port = int(ui.config("web", "port", 8000))
171 port = int(ui.config("web", "port", 8000))
167 use_ipv6 = ui.configbool("web", "ipv6")
172 use_ipv6 = ui.configbool("web", "ipv6")
168 webdir_conf = ui.config("web", "webdir_conf")
173 webdir_conf = ui.config("web", "webdir_conf")
169 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
174 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
170 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
175 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
171
176
172 if use_threads:
177 if use_threads:
173 try:
178 try:
174 from threading import activeCount
179 from threading import activeCount
175 except ImportError:
180 except ImportError:
176 use_threads = False
181 use_threads = False
177
182
178 if use_threads:
183 if use_threads:
179 _mixin = SocketServer.ThreadingMixIn
184 _mixin = SocketServer.ThreadingMixIn
180 else:
185 else:
181 if hasattr(os, "fork"):
186 if hasattr(os, "fork"):
182 _mixin = SocketServer.ForkingMixIn
187 _mixin = SocketServer.ForkingMixIn
183 else:
188 else:
184 class _mixin: pass
189 class _mixin: pass
185
190
186 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
191 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
187 def __init__(self, *args, **kargs):
192 def __init__(self, *args, **kargs):
188 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
193 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
189 self.accesslog = accesslog
194 self.accesslog = accesslog
190 self.errorlog = errorlog
195 self.errorlog = errorlog
191 self.repo = repo
196 self.repo = repo
192 self.webdir_conf = webdir_conf
197 self.webdir_conf = webdir_conf
193 self.webdirmaker = hgwebdir
198 self.webdirmaker = hgwebdir
194 self.repoviewmaker = hgweb
199 self.repoviewmaker = hgweb
195 self.reqmaker = wsgiapplication(self.make_handler)
200 self.reqmaker = wsgiapplication(self.make_handler)
196
201
197 def make_handler(self):
202 def make_handler(self):
198 if self.webdir_conf:
203 if self.webdir_conf:
199 hgwebobj = self.webdirmaker(self.webdir_conf)
204 hgwebobj = self.webdirmaker(self.webdir_conf)
200 elif self.repo is not None:
205 elif self.repo is not None:
201 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
206 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
202 repo.origroot))
207 repo.origroot))
203 else:
208 else:
204 raise hg.RepoError(_('no repo found'))
209 raise hg.RepoError(_('no repo found'))
205 return hgwebobj
210 return hgwebobj
206
211
207 class IPv6HTTPServer(MercurialHTTPServer):
212 class IPv6HTTPServer(MercurialHTTPServer):
208 address_family = getattr(socket, 'AF_INET6', None)
213 address_family = getattr(socket, 'AF_INET6', None)
209
214
210 def __init__(self, *args, **kwargs):
215 def __init__(self, *args, **kwargs):
211 if self.address_family is None:
216 if self.address_family is None:
212 raise hg.RepoError(_('IPv6 not available on this system'))
217 raise hg.RepoError(_('IPv6 not available on this system'))
213 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
218 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
214
219
215 if use_ipv6:
220 if use_ipv6:
216 return IPv6HTTPServer((address, port), _hgwebhandler)
221 return IPv6HTTPServer((address, port), _hgwebhandler)
217 else:
222 else:
218 return MercurialHTTPServer((address, port), _hgwebhandler)
223 return MercurialHTTPServer((address, port), _hgwebhandler)
General Comments 0
You need to be logged in to leave comments. Login now