Mercurial > public > mercurial-scm > hg
comparison mercurial/hgweb/hgweb_mod.py @ 26134:e0a6908f066f
hgweb: establish class for holding per request context
Currently, hgweb applications have many instance variables holding
mutated state. This is somewhat problematic because multiple threads
may race accessing or changing this state.
This patch starts a series that will add more thread safety to
hgweb applications. It will do this by moving mutated state out
of hgweb and into per-request instances of the newly established
"requestcontext" class.
Our new class currently behaves like a proxy to hgweb instances. This
should change once all state is captured in it instead of hgweb. The
effectiveness of this proxy is demonstrated by passing instances of
it - not hgweb instances/self - to various functions.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 22 Aug 2015 14:59:36 -0700 |
parents | 44ed220ef26f |
children | edfb4d3b9672 |
comparison
equal
deleted
inserted
replaced
26133:44ed220ef26f | 26134:e0a6908f066f |
---|---|
58 break | 58 break |
59 breadcrumb.append({'url': urlel, 'name': pathel}) | 59 breadcrumb.append({'url': urlel, 'name': pathel}) |
60 urlel = os.path.dirname(urlel) | 60 urlel = os.path.dirname(urlel) |
61 return reversed(breadcrumb) | 61 return reversed(breadcrumb) |
62 | 62 |
63 | |
64 class requestcontext(object): | |
65 """Holds state/context for an individual request. | |
66 | |
67 Servers can be multi-threaded. Holding state on the WSGI application | |
68 is prone to race conditions. Instances of this class exist to hold | |
69 mutable and race-free state for requests. | |
70 """ | |
71 def __init__(self, app): | |
72 object.__setattr__(self, 'app', app) | |
73 object.__setattr__(self, 'repo', app.repo) | |
74 | |
75 # Proxy unknown reads and writes to the application instance | |
76 # until everything is moved to us. | |
77 def __getattr__(self, name): | |
78 return getattr(self.app, name) | |
79 | |
80 def __setattr__(self, name, value): | |
81 return setattr(self.app, name, value) | |
63 | 82 |
64 class hgweb(object): | 83 class hgweb(object): |
65 """HTTP server for individual repositories. | 84 """HTTP server for individual repositories. |
66 | 85 |
67 Instances of this class serve HTTP responses for a particular | 86 Instances of this class serve HTTP responses for a particular |
191 | 210 |
192 This is typically only called by Mercurial. External consumers | 211 This is typically only called by Mercurial. External consumers |
193 should be using instances of this class as the WSGI application. | 212 should be using instances of this class as the WSGI application. |
194 """ | 213 """ |
195 self.refresh(req) | 214 self.refresh(req) |
215 rctx = requestcontext(self) | |
196 | 216 |
197 # work with CGI variables to create coherent structure | 217 # work with CGI variables to create coherent structure |
198 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME | 218 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME |
199 | 219 |
200 req.url = req.env['SCRIPT_NAME'] | 220 req.url = req.env['SCRIPT_NAME'] |
221 if protocol.iscmd(cmd): | 241 if protocol.iscmd(cmd): |
222 try: | 242 try: |
223 if query: | 243 if query: |
224 raise ErrorResponse(HTTP_NOT_FOUND) | 244 raise ErrorResponse(HTTP_NOT_FOUND) |
225 if cmd in perms: | 245 if cmd in perms: |
226 self.check_perm(req, perms[cmd]) | 246 self.check_perm(rctx, req, perms[cmd]) |
227 return protocol.call(self.repo, req, cmd) | 247 return protocol.call(self.repo, req, cmd) |
228 except ErrorResponse as inst: | 248 except ErrorResponse as inst: |
229 # A client that sends unbundle without 100-continue will | 249 # A client that sends unbundle without 100-continue will |
230 # break if we respond early. | 250 # break if we respond early. |
231 if (cmd == 'unbundle' and | 251 if (cmd == 'unbundle' and |
282 ctype = tmpl('mimetype', encoding=encoding.encoding) | 302 ctype = tmpl('mimetype', encoding=encoding.encoding) |
283 ctype = templater.stringify(ctype) | 303 ctype = templater.stringify(ctype) |
284 | 304 |
285 # check read permissions non-static content | 305 # check read permissions non-static content |
286 if cmd != 'static': | 306 if cmd != 'static': |
287 self.check_perm(req, None) | 307 self.check_perm(rctx, req, None) |
288 | 308 |
289 if cmd == '': | 309 if cmd == '': |
290 req.form['cmd'] = [tmpl.cache['default']] | 310 req.form['cmd'] = [tmpl.cache['default']] |
291 cmd = req.form['cmd'][0] | 311 cmd = req.form['cmd'][0] |
292 | 312 |
295 if cmd not in webcommands.__all__: | 315 if cmd not in webcommands.__all__: |
296 msg = 'no such method: %s' % cmd | 316 msg = 'no such method: %s' % cmd |
297 raise ErrorResponse(HTTP_BAD_REQUEST, msg) | 317 raise ErrorResponse(HTTP_BAD_REQUEST, msg) |
298 elif cmd == 'file' and 'raw' in req.form.get('style', []): | 318 elif cmd == 'file' and 'raw' in req.form.get('style', []): |
299 self.ctype = ctype | 319 self.ctype = ctype |
300 content = webcommands.rawfile(self, req, tmpl) | 320 content = webcommands.rawfile(rctx, req, tmpl) |
301 else: | 321 else: |
302 content = getattr(webcommands, cmd)(self, req, tmpl) | 322 content = getattr(webcommands, cmd)(rctx, req, tmpl) |
303 req.respond(HTTP_OK, ctype) | 323 req.respond(HTTP_OK, ctype) |
304 | 324 |
305 return content | 325 return content |
306 | 326 |
307 except (error.LookupError, error.RepoLookupError) as err: | 327 except (error.LookupError, error.RepoLookupError) as err: |
440 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None), | 460 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None), |
441 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None), | 461 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None), |
442 'zip': ('application/zip', 'zip', '.zip', None), | 462 'zip': ('application/zip', 'zip', '.zip', None), |
443 } | 463 } |
444 | 464 |
445 def check_perm(self, req, op): | 465 def check_perm(self, rctx, req, op): |
446 for permhook in permhooks: | 466 for permhook in permhooks: |
447 permhook(self, req, op) | 467 permhook(rctx, req, op) |