Skip to content

Conversation

@josejulio
Copy link
Contributor

Fixes cherrypy/cherrypy#1404

  • What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Bugfix

  • What is the related issue number (starting with #)

cherrypy/cherrypy#1404

  • What is the current behavior? (You can also link to an open issue here)

on_end_request hook is being call before sending a response on cherrypy

  • What is the new behavior (if this is a feature change)?

on_end_request hook is being call after sending a response on cherrypy

  • Other information:

I found Lawouach/WebSocket-for-Python#158 which lead me to this "PR" https://bitbucket.org/cherrypy/cherrypy/pull-requests/121/fixing-issue-1404-on_end_request-hook-is/diff
I ported that to cheroot and addressed the last comment from "Sylvain Hellegouarch"

@josejulio josejulio force-pushed the on_end_request_before_response branch 2 times, most recently from 959fd81 to ebedc24 Compare February 9, 2018 04:39
@codecov
Copy link

codecov bot commented Feb 9, 2018

Codecov Report

Merging #75 into master will increase coverage by 0.33%.
The diff coverage is 96.96%.

@@           Coverage Diff            @@
##           master    #75      +/-   ##
========================================
+ Coverage   65.67%    66%   +0.33%     
========================================
  Files          15     15              
  Lines        2753   2780      +27     
========================================
+ Hits         1808   1835      +27     
  Misses        945    945

@webknjaz webknjaz requested review from a team, Lawouach, jaraco and webknjaz February 9, 2018 10:39
@webknjaz
Copy link
Member

webknjaz commented Feb 9, 2018

Original PR authors/reviewers seem to be: @moigagoo @ronin8600 @Lawouach

@webknjaz
Copy link
Member

webknjaz commented Feb 9, 2018

Thanks for the PR.
I cannot accept it w/o manually checking the flow you're changing, so I'll do this once I have some time. This should also trigger checks against CherryPy itself.
Covering this with tests might speed up the process.

@josejulio
Copy link
Contributor Author

Thanks @webknjaz
I'll try to make time to write test.
Basically this writes the headers before closing the request.
See here and here

I suppose (haven't digged much on cherrypy code) cherrypy hooks on that close and triggers the on_end_request

@josejulio josejulio force-pushed the on_end_request_before_response branch 2 times, most recently from 482f05b to 562913c Compare February 13, 2018 05:32
@josejulio josejulio changed the title Ensure response headers are being sent before calling on_end_request hook Ensure response headers are being sent before closing the response Feb 13, 2018
@josejulio
Copy link
Contributor Author

@webknjaz I added a test, I verified that it fails without this patch - and it passes with the patch :) -

Let me know how this can be improved.

@josejulio josejulio force-pushed the on_end_request_before_response branch from 562913c to c17bff6 Compare February 13, 2018 05:44
@webknjaz
Copy link
Member

@josejulio first of all, linters in CI fail: https://travis-ci.org/cherrypy/cheroot/jobs/340814348#L484
We use fail fast approach in our CI to save on computing resources and power, so tests are not even being run if fast checks crash.
Could you please correct this?

P.S. Also, it's possible to pip install pre-commit && pre-commit install locally to set up automatic linter running for each commit you do on your machine :) So that you won't wait for CI to be triggered and return the result.

Copy link
Member

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus code needs to bypass linters


def test_send_header_before_closing(testing_server_close):
"""Tests we are actually sending the headers before closing the response."""
testing_server_close.server_client.get('/')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about checking http response headers in client?

resp_status_line, resp_headers, resp_body = testing_server_close.server_client.get('/')

Copy link
Contributor Author

@josejulio josejulio Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, maybe my description is wrong here or i didn't understand well.

The headers are always sent, the problem is that the close [1] method is being call before the headers are being sent.

This is a problem in cherrypy, because the on_end_request is being called before the headers are actually sent, anything sent to the client from this hook will reach before the headers, thus the headers will be malformed. I don't think I can test this from the client side unless i can get a raw response, but i would still need to send something on that close [1] method.

It's done in a similar fashion in pep-0333 [2] (talking about the fix, not the tests)

    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

[1] https://github.com/cherrypy/cheroot/pull/75/files#diff-08d927a6f63bcf494ed88cca2a6873d0R144
[2] https://www.python.org/dev/peps/pep-0333/

def close(self):
"""Hook for close, tests if headers already sent."""
self.sent_headers_before_closing = \
self.req is not None and self.req.sent_headers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be overengineered. Please see my comment below.

@josejulio josejulio force-pushed the on_end_request_before_response branch from 376de01 to bb2ddde Compare February 16, 2018 05:44
@josejulio
Copy link
Contributor Author

@webknjaz I fixed linting issues and tried to simplify the test.

Here is the output of the test without the fix (commit: 742b1b9)

―――――――――――――――――――――――――――――――――――――― test_send_header_before_closing ――――――――――――――――――――――――――――――――――――――

testing_server_close = <cheroot.wsgi.Server object at 0x7fc57f98a350>

    def test_send_header_before_closing(testing_server_close):
        """Test we are actually sending the headers before calling 'close'."""
        _, _, resp_body = testing_server_close.server_client.get('/')
>       assert resp_body == 'hello'
E       AssertionError: assert 'helloHTTP/1....\r\n0\r\n\r\n' == 'hello'
E         + hello
E         - helloHTTP/1.1 200 OK\r
E         - Content-Type: text/html\r
E         - Transfer-Encoding: chunked\r
E         - Date: Fri, 16 Feb 2018 05:30:56 GMT\r
E         - Server: Cheroot/6.0.1.dev103+gc17bff6d\r
E         - \r...
E         
E         ...Full output truncated (3 lines hidden), use '-vv' to show

cheroot/test/test_core.py:402: AssertionError

@josejulio
Copy link
Contributor Author

@webknjaz @jaraco @Lawouach
Is there anything else I can do here? Do you have additional comments on the tests?
Thanks.


if (self.ready and not self.sent_headers):
self.sent_headers = True
self.send_headers()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why completely remove this? How will it affect non-WSGI plain HTTP server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though that adding it to self.server.gateway(self).respond() would cover it

I suppose I can add it back to ensure we are always sending the headers in case the gateway is not sending the headers.


def close(self):
"""Hook for close, write hello."""
self.req.write('hello'.encode('utf-8'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b'hello' would look nicer

cheroot/wsgi.py Outdated
self.write(chunk)
finally:
# Send headers if not already sent
if not self.req.sent_headers:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's always paired with "if not sent" check. Try moving it inside the method and maybe rename it into smth like ensure_headers_sent or so

@webknjaz
Copy link
Member

@josejulio sorry for late reply, I've added some thoughts on this

@josejulio
Copy link
Contributor Author

@josejulio sorry for late reply, I've added some thoughts on this

Better late than never :).
I'll address your comments later today, thanks!

  Moved the test if the headers are sent inside the ensure_headers_sent
   (renamed from write_headers)
  Puts back a "send headers" test.
@josejulio
Copy link
Contributor Author

@webknjaz Addressed the comments, travis is green and this is ready for other review round, thanks!

self.rfile = KnownLengthRFile(self.conn.rfile, cl)

self.server.gateway(self).respond()
self.ensure_headers_sent()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you've lost ready check here. Could be:

self.ready and self.ensure_headers_sent()

Is this intentional? What is the effect of a such change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not intentional, missed that, thanks for spotting it!

@josejulio
Copy link
Contributor Author

@webknjaz Forgot to say that addressed your latest comments, thanks

@webknjaz
Copy link
Member

webknjaz commented Apr 4, 2018

LGTM! Thank you @josejulio!

@webknjaz webknjaz merged commit 513f789 into cherrypy:master Apr 4, 2018
@webknjaz
Copy link
Member

webknjaz commented Apr 4, 2018

@josejulio This code should reach PYPI soon, when this build is complete: https://travis-ci.org/cherrypy/cheroot/builds/362369631

It should become available at https://pypi.org/project/Cheroot/6.1.0/ in about 30 min from now.

@josejulio
Copy link
Contributor Author

Awesome, thank you!

@josejulio josejulio deleted the on_end_request_before_response branch July 27, 2023 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"on_end_request" hook is getting called before the response header is sent

2 participants