D2775: hgweb: create dedicated type for WSGI responses
This revision was automatically updated to reflect the committed changes. Closed by commit rHGa88d68dc3ee8: hgweb: create dedicated type for WSGI responses (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2775?vs=6837&id=6914 REVISION DETAIL https://phab.mercurial-scm.org/D2775 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/hgweb/request.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -149,18 +149,18 @@ def iscmd(cmd): return cmd in wireproto.commands -def handlewsgirequest(rctx, wsgireq, req, checkperm): +def handlewsgirequest(rctx, wsgireq, req, res, checkperm): """Possibly process a wire protocol request. If the current request is a wire protocol request, the request is processed by this function. ``wsgireq`` is a ``wsgirequest`` instance. ``req`` is a ``parsedrequest`` instance. +``res`` is a ``wsgiresponse`` instance. -Returns a 2-tuple of (bool, response) where the 1st element indicates -whether the request was handled and the 2nd element is a return -value for a WSGI application (often a generator of bytes). +Returns a bool indicating if the request was serviced. If set, the caller +should stop processing the request, as a response has already been issued. """ # Avoid cycle involving hg module. from .hgweb import common as hgwebcommon @@ -171,7 +171,7 @@ # string parameter. If it isn't present, this isn't a wire protocol # request. if 'cmd' not in req.querystringdict: -return False, None +return False cmd = req.querystringdict['cmd'][0] @@ -183,18 +183,19 @@ # known wire protocol commands and it is less confusing for machine # clients. if not iscmd(cmd): -return False, None +return False # The "cmd" query string argument is only valid on the root path of the # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request # in this case. We send an HTTP 404 for backwards compatibility reasons. if req.dispatchpath: -res = _handlehttperror( -hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, -req) - -return True, res +res.status = hgwebcommon.statusmessage(404) +res.headers['Content-Type'] = HGTYPE +# TODO This is not a good response to issue for this request. This +# is mostly for BC for now. +res.setbodybytes('0\n%s\n' % b'Not Found') +return True proto = httpv1protocolhandler(wsgireq, req, repo.ui, lambda perm: checkperm(rctx, wsgireq, perm)) @@ -204,11 +205,16 @@ # exception here. So consider refactoring into a exception type that # is associated with the wire protocol. try: -res = _callhttp(repo, wsgireq, req, proto, cmd) +_callhttp(repo, wsgireq, req, res, proto, cmd) except hgwebcommon.ErrorResponse as e: -res = _handlehttperror(e, wsgireq, req) +for k, v in e.headers: +res.headers[k] = v +res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) +# TODO This response body assumes the failed command was +# "unbundle." That assumption is not always valid. +res.setbodybytes('0\n%s\n' % pycompat.bytestr(e)) -return True, res +return True def _httpresponsetype(ui, req, prefer_uncompressed): """Determine the appropriate response type and compression settings. @@ -250,7 +256,10 @@ opts = {'level': ui.configint('server', 'zliblevel')} return HGTYPE, util.compengines['zlib'], opts -def _callhttp(repo, wsgireq, req, proto, cmd): +def _callhttp(repo, wsgireq, req, res, proto, cmd): +# Avoid cycle involving hg module. +from .hgweb import common as hgwebcommon + def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header # identifying the compression engine. @@ -262,26 +271,35 @@ for chunk in gen: yield chunk +def setresponse(code, contenttype, bodybytes=None, bodygen=None): +if code == HTTP_OK: +res.status = '200 Script output follows' +else: +res.status = hgwebcommon.statusmessage(code) + +res.headers['Content-Type'] = contenttype + +if bodybytes is not None: +res.setbodybytes(bodybytes) +if bodygen is not None: +res.setbodygen(bodygen) + if not wireproto.commands.commandavailable(cmd, proto): -wsgireq.respond(HTTP_OK, HGERRTYPE, -body=_('requested wire protocol command is not ' - 'availabl
D2775: hgweb: create dedicated type for WSGI responses
indygreg updated this revision to Diff 6837. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2775?vs=6821&id=6837 REVISION DETAIL https://phab.mercurial-scm.org/D2775 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/hgweb/request.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -149,18 +149,18 @@ def iscmd(cmd): return cmd in wireproto.commands -def handlewsgirequest(rctx, wsgireq, req, checkperm): +def handlewsgirequest(rctx, wsgireq, req, res, checkperm): """Possibly process a wire protocol request. If the current request is a wire protocol request, the request is processed by this function. ``wsgireq`` is a ``wsgirequest`` instance. ``req`` is a ``parsedrequest`` instance. +``res`` is a ``wsgiresponse`` instance. -Returns a 2-tuple of (bool, response) where the 1st element indicates -whether the request was handled and the 2nd element is a return -value for a WSGI application (often a generator of bytes). +Returns a bool indicating if the request was serviced. If set, the caller +should stop processing the request, as a response has already been issued. """ # Avoid cycle involving hg module. from .hgweb import common as hgwebcommon @@ -171,7 +171,7 @@ # string parameter. If it isn't present, this isn't a wire protocol # request. if 'cmd' not in req.querystringdict: -return False, None +return False cmd = req.querystringdict['cmd'][0] @@ -183,18 +183,19 @@ # known wire protocol commands and it is less confusing for machine # clients. if not iscmd(cmd): -return False, None +return False # The "cmd" query string argument is only valid on the root path of the # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request # in this case. We send an HTTP 404 for backwards compatibility reasons. if req.dispatchpath: -res = _handlehttperror( -hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, -req) - -return True, res +res.status = hgwebcommon.statusmessage(404) +res.headers['Content-Type'] = HGTYPE +# TODO This is not a good response to issue for this request. This +# is mostly for BC for now. +res.setbodybytes('0\n%s\n' % b'Not Found') +return True proto = httpv1protocolhandler(wsgireq, req, repo.ui, lambda perm: checkperm(rctx, wsgireq, perm)) @@ -204,11 +205,16 @@ # exception here. So consider refactoring into a exception type that # is associated with the wire protocol. try: -res = _callhttp(repo, wsgireq, req, proto, cmd) +_callhttp(repo, wsgireq, req, res, proto, cmd) except hgwebcommon.ErrorResponse as e: -res = _handlehttperror(e, wsgireq, req) +for k, v in e.headers: +res.headers[k] = v +res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) +# TODO This response body assumes the failed command was +# "unbundle." That assumption is not always valid. +res.setbodybytes('0\n%s\n' % pycompat.bytestr(e)) -return True, res +return True def _httpresponsetype(ui, req, prefer_uncompressed): """Determine the appropriate response type and compression settings. @@ -250,7 +256,10 @@ opts = {'level': ui.configint('server', 'zliblevel')} return HGTYPE, util.compengines['zlib'], opts -def _callhttp(repo, wsgireq, req, proto, cmd): +def _callhttp(repo, wsgireq, req, res, proto, cmd): +# Avoid cycle involving hg module. +from .hgweb import common as hgwebcommon + def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header # identifying the compression engine. @@ -262,26 +271,35 @@ for chunk in gen: yield chunk +def setresponse(code, contenttype, bodybytes=None, bodygen=None): +if code == HTTP_OK: +res.status = '200 Script output follows' +else: +res.status = hgwebcommon.statusmessage(code) + +res.headers['Content-Type'] = contenttype + +if bodybytes is not None: +res.setbodybytes(bodybytes) +if bodygen is not None: +res.setbodygen(bodygen) + if not wireproto.commands.commandavailable(cmd, proto): -wsgireq.respond(HTTP_OK, HGERRTYPE, -body=_('requested wire protocol command is not ' - 'available over HTTP')) -return [] +setresponse(HTTP_OK, HGERRTYPE, +_('reques
D2775: hgweb: create dedicated type for WSGI responses
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We have refactored the request side of WSGI processing into a dedicated type. Now let's do the same thing for the response side. We invent a ``wsgiresponse`` type. It takes an instance of a request (for consulation) and the WSGI application's "start_response" handler. The type basically allows setting the HTTP status line, response headers, and the response body. The WSGI application calls sendresponse() to start sending output. Output is emitted as a generator to be fed through the WSGI application. According to PEP-, this is the preferred way for output to be transmitted. (Our legacy ``wsgirequest`` exposed a write() to send data. We do not wish to support this API because it isn't recommended by PEP-.) The wire protocol code has been ported to use the new API. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2775 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/hgweb/request.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -149,18 +149,18 @@ def iscmd(cmd): return cmd in wireproto.commands -def handlewsgirequest(rctx, wsgireq, req, checkperm): +def handlewsgirequest(rctx, wsgireq, req, res, checkperm): """Possibly process a wire protocol request. If the current request is a wire protocol request, the request is processed by this function. ``wsgireq`` is a ``wsgirequest`` instance. ``req`` is a ``parsedrequest`` instance. +``res`` is a ``wsgiresponse`` instance. -Returns a 2-tuple of (bool, response) where the 1st element indicates -whether the request was handled and the 2nd element is a return -value for a WSGI application (often a generator of bytes). +Returns a bool indicating if the request was serviced. If set, the caller +should stop processing the request, as a response has already been issued. """ # Avoid cycle involving hg module. from .hgweb import common as hgwebcommon @@ -171,7 +171,7 @@ # string parameter. If it isn't present, this isn't a wire protocol # request. if 'cmd' not in req.querystringdict: -return False, None +return False cmd = req.querystringdict['cmd'][0] @@ -183,18 +183,19 @@ # known wire protocol commands and it is less confusing for machine # clients. if not iscmd(cmd): -return False, None +return False # The "cmd" query string argument is only valid on the root path of the # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request # in this case. We send an HTTP 404 for backwards compatibility reasons. if req.dispatchpath: -res = _handlehttperror( -hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, -req) - -return True, res +res.status = hgwebcommon.statusmessage(404) +res.headers['Content-Type'] = HGTYPE +# TODO This is not a good response to issue for this request. This +# is mostly for BC for now. +res.setbodybytes('0\n%s\n' % b'commands not available at this URL') +return True proto = httpv1protocolhandler(wsgireq, req, repo.ui, lambda perm: checkperm(rctx, wsgireq, perm)) @@ -204,11 +205,16 @@ # exception here. So consider refactoring into a exception type that # is associated with the wire protocol. try: -res = _callhttp(repo, wsgireq, req, proto, cmd) +_callhttp(repo, wsgireq, req, res, proto, cmd) except hgwebcommon.ErrorResponse as e: -res = _handlehttperror(e, wsgireq, req) +for k, v in e.headers: +res.headers[k] = v +res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) +# TODO This response body assumes the failed command was +# "unbundle." That assumption is not always valid. +res.setbodybytes('0\n%s\n' % pycompat.bytestr(e)) -return True, res +return True def _httpresponsetype(ui, req, prefer_uncompressed): """Determine the appropriate response type and compression settings. @@ -250,7 +256,10 @@ opts = {'level': ui.configint('server', 'zliblevel')} return HGTYPE, util.compengines['zlib'], opts -def _callhttp(repo, wsgireq, req, proto, cmd): +def _callhttp(repo, wsgireq, req, res, proto, cmd): +# Avoid cycle involving hg module. +from .hgweb import common as hgwebcommon + def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header # identifying the compression engine. @@ -262,26 +271,35 @@