D2775: hgweb: create dedicated type for WSGI responses

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-10 Thread indygreg (Gregory Szorc)
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

2018-03-09 Thread indygreg (Gregory Szorc)
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 @@