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