[PATCH STABLE] hgweb: send proper HTTP response after uncaught exception

Augie Fackler raf at durin42.com
Fri Nov 28 21:09:05 CST 2014


On Nov 28, 2014, at 2:05 PM, Gregory Szorc <gregory.szorc at gmail.com> wrote:

> # HG changeset patch
> # User Gregory Szorc <gregory.szorc at gmail.com>
> # Date 1417201142 28800
> #      Fri Nov 28 10:59:02 2014 -0800
> # Branch stable
> # Node ID 87760fa6c70b51035f9b504393791fd86cde6e22
> # Parent  edf29f9c15f0f171847f4c7a8184cca4e95c8b31
> hgweb: send proper HTTP response after uncaught exception

Nice work! Queued.

> 
> This patch fixes a bug where hgweb would send an incomplete HTTP
> response.
> 
> If an uncaught exception is raised when hgweb is processing a request,
> hgweb attempts to send a generic error response and log that exception.
> 
> The server defaults to chunked transfer coding. If an uncaught exception
> occurred, it was sending the error response string / chunk properly.
> However, RFC 7230 Section 4.1 mandates a 0 size last chunk be sent to
> indicate end of the entity body. hgweb was failing to send this last
> chunk. As a result, properly written HTTP clients would assume more data
> was coming and they would likely time out waiting for another chunk to
> arrive.
> 
> Mercurial's own test harness was paving over the improper HTTP behavior
> by not attempting to read the response body if the status code was 500.
> This incorrect workaround was added in ba6577a19656 and has been removed
> with this patch.
> 
> diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
> --- a/mercurial/hgweb/server.py
> +++ b/mercurial/hgweb/server.py
> @@ -80,8 +80,9 @@ class _httprequesthandler(BaseHTTPServer
>             self.do_write()
>         except Exception:
>             self._start_response("500 Internal Server Error", [])
>             self._write("Internal Server Error")
> +            self._done()
>             tb = "".join(traceback.format_exception(*sys.exc_info()))
>             self.log_error("Exception happened during processing "
>                            "request '%s':\n%s", self.path, tb)
> 
> diff --git a/tests/get-with-headers.py b/tests/get-with-headers.py
> --- a/tests/get-with-headers.py
> +++ b/tests/get-with-headers.py
> @@ -42,11 +42,10 @@ def request(host, path, show):
>         if response.getheader(h, None) is not None:
>             print "%s: %s" % (h, response.getheader(h))
>     if not headeronly:
>         print
> -        if response.status != 500:
> -            data = response.read()
> -            sys.stdout.write(data)
> +        data = response.read()
> +        sys.stdout.write(data)
> 
>         if twice and response.getheader('ETag', None):
>             tag = response.getheader('ETag')
> 
> diff --git a/tests/hgweberror.py b/tests/hgweberror.py
> new file mode 100644
> --- /dev/null
> +++ b/tests/hgweberror.py
> @@ -0,0 +1,17 @@
> +# A dummy extension that installs an hgweb command that throws an Exception.
> +
> +from mercurial.hgweb import webcommands
> +
> +def raiseerror(web, req, tmpl):
> +    '''Dummy web command that raises an uncaught Exception.'''
> +
> +    # Simulate an error after partial response.
> +    if 'partialresponse' in req.form:
> +        req.respond(200, 'text/plain')
> +        req.write('partial content\n')
> +
> +    raise Exception('I am an uncaught error!')
> +
> +def extsetup(ui):
> +    setattr(webcommands, 'raiseerror', raiseerror)
> +    webcommands.__all__.append('raiseerror')
> diff --git a/tests/test-hgweb.t b/tests/test-hgweb.t
> --- a/tests/test-hgweb.t
> +++ b/tests/test-hgweb.t
> @@ -578,5 +578,31 @@ phase changes are refreshed (issue4061)
> errors
> 
>   $ cat errors.log
> 
> +Uncaught exceptions result in a logged error and canned HTTP response
> +
> +  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
> +  $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
> +  $ cat hg.pid >> $DAEMON_PIDS
> +
> +  $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
> +  500 Internal Server Error
> +  transfer-encoding: chunked
> +  
> +  Internal Server Error (no-eol)
> +  [1]
> +
> +  $ head -1 errors.log
> +  .* Exception happened during processing request '/raiseerror': (re)
> +
> +Uncaught exception after partial content sent
> +
> +  $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
> +  200 Script output follows
> +  transfer-encoding: chunked
> +  content-type: text/plain
> +  
> +  partial content
> +  Internal Server Error (no-eol)
> +
>   $ cd ..
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: Message signed with OpenPGP using GPGMail
URL: <http://selenic.com/pipermail/mercurial-devel/attachments/20141128/b3b2ff56/attachment.pgp>


More information about the Mercurial-devel mailing list