Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EmptyResponse class #1270

Closed
wants to merge 7 commits into from

Conversation

declaresub
Copy link

@declaresub declaresub commented Aug 18, 2021

Closes #1178, #1099


In particular, use an EmptyResponse object for 1xx, 204, 205, 304 responses as it sets or omits a Content-Length header as appropriate.

Signature: `Response(status_code:int, headers: typing.Optional[typing.Dict[str, str]] = None, background: typing.Optional[BackgroundTask] = None)`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Signature: `Response(status_code:int, headers: typing.Optional[typing.Dict[str, str]] = None, background: typing.Optional[BackgroundTask] = None)`
Signature: `EmptyResponse(status_code:int, headers: typing.Optional[typing.Dict[str, str]] = None, background: typing.Optional[BackgroundTask] = None)`

Alternatively, just take this line out. We don't have proper, generated API docs so trying to be consistent is difficult.

Copy link
Author

Choose a reason for hiding this comment

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

I fixed the class name. Following the superclass documentation seemed like the right thing to do.

@JayH5 JayH5 changed the title Implement EmptyResponse class (#1178). Implement EmptyResponse class Aug 18, 2021
@tomchristie
Copy link
Member

I'm not convinced that we'd need a separate response class to deal with this - although maybe I'v not looked into it enough.
Don't recall Django, Flask, and others taking this approach. Can't we instead make sure that Response either includes or doesn't include the appropriate headers depending on the status code?

@declaresub
Copy link
Author

You certainly could take the approach of adding more special-case code to Response. Since the status_code attribute can be set after initialization, I believe this approach would best be done in Response.call. There you would check the value of self.status_code, modify headers as appropriate just before sending, and send an empty body for the status codes that require it.

I would be willing to do this.

@Kludex
Copy link
Sponsor Member

Kludex commented Dec 16, 2021

I'm not convinced that we'd need a separate response class to deal with this - although maybe I'v not looked into it enough. Don't recall Django, Flask, and others taking this approach. Can't we instead make sure that Response either includes or doesn't include the appropriate headers depending on the status code?

Flask: They do the same as what this PR proposes.

Django:

@declaresub
Copy link
Author

I have revised the pull request to move handling of responses with status code 1xx, 204, 205, 304 into the Response class.

This is probably the better approach, as users can just set the status code, and the right thing should happen. Also it fits better with fastapi, and its use of JSONResponse by default.

Comment on lines +140 to +144
# http spec does not appear to say whether or not there can be
# a content-type header. Some clients
# will attempt to parse the message body if there is a content-type
# header, so we ensure that there isn't one in
# the response.
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Why? Is it obvious?

Shouldn't we focus on what is defined?

@Kludex
Copy link
Sponsor Member

Kludex commented Dec 16, 2021

I'm extremely happy about the changes here. :)

Thank you @declaresub ! 🎉

This is an important PR, so I'll feel more comfortable having @tomchristie 's approval.

@Kludex Kludex mentioned this pull request Dec 20, 2021
8 tasks
@tomchristie
Copy link
Member

This is not at all clear to me.
For instance, while it's related to #1099, it doesn't appear to close it.

Implementation-wise I'd expect any "do we generate a Content-Length header or not" to be handled in the init_headers() method, but let's not start from that point.

Can we take this back to basics first...

Issue #1099 describes things at the API level that I'd like to see:


Expected behavior

>>> from starlette.responses import RedirectResponse
>>> RedirectResponse(url="/", status_code=302).headers
MutableHeaders({'content-length': '0', 'location': '/'})

I expect there to be a content-length field specifying zero length

Actual behavior

>>> from starlette.responses import RedirectResponse
>>> RedirectResponse(url="/", status_code=302).headers
MutableHeaders({'location': '/'})

Header has no content-length field.


The issue here isn't that we ought to be stripping the Content-Length header in several cases, but rather than we're not adding a correct Content-Length: 0 header in cases where there is no body.

Which is what #1099 describes...

The Content-Length header field is not set for cases where body is empty. In some cases this seems to not be according to the HTTP/1.1 spec and creates troubles when using for example traefik as a proxy in front of a starlette-based backend.

The sensible tack here would be to deal with that first.

I'd expect a pull request there to have a unit test at the API level of eg...

def test_empty_response():
    response = Response()
    assert response.headers["Content-Length"] == "0"

And that the implementation to resolve this would be in init_headers().

Once we have that PR we'd then want to take a look at refining it to deal with the 1xx, 204, and 304 cases.

@declaresub
Copy link
Author

I agree that #1099 is not closed here, but I don't think that fixing it in init_headers is the best approach. Indeed, were I to do this, I would start by getting rid of that method. Then I would move the invocation of render from init into call, and set the content-* headers at that time.

@declaresub
Copy link
Author

And, at this point, I wonder whether the right thing to do would be to create a new issue that describes the need to set body and content-length appropriately for all combinations of request method and status code, then close the existing issues #1270, #1099, and maybe corresponding pull requests, with a reference to this new issue that describes the goal.

@tomchristie
Copy link
Member

@declaresub - I'd like to close this PR and #1178 - an EmptyResponse class isn't the API we're looking for here.

#1099 is a valid issue, and properly described, so let's leave it open.

#1395 is the start of a PR addressing that issue.

@tomchristie
Copy link
Member

Thanks for having spent some time looking into this, and for prompting the issue to get addressed.
An EmptyResponse class isn't the API I'd want to see here (also eg. not what we see in flask, django).

Closing in favour of #1397.

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.

Add EmptyResponse class
4 participants