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

JSONResponse Wrong Content-Length Value #1282

Closed
2 tasks done
accredited-unicorn opened this issue Sep 7, 2021 · 4 comments · Fixed by #1397
Closed
2 tasks done

JSONResponse Wrong Content-Length Value #1282

accredited-unicorn opened this issue Sep 7, 2021 · 4 comments · Fixed by #1397

Comments

@accredited-unicorn
Copy link

Describe the bug

JSONResponse populates the response header with a non-zero Content-Length whenever the response object is instantiated without any content.

Checklist

  • The bug is reproducible against the latest release and/or master.
  • There are no similar issues or pull requests to fix it yet.

To reproduce

from starlette.datastructures import MutableHeaders
from starlette.responses import JSONResponse

r1 = JSONResponse('')
r1.headers

r2 = JSONResponse()
r2.headers

Expected behavior

( r1.headers == r2.headers
  and
  r1.headers == MutableHeaders({'content-length': '0', 'content-type': 'application/json'}) )

Actual behavior

( r1.headers != r2.headers
  and
  r1.headers == MutableHeaders({'content-length': '2', 'content-type': 'application/json'})
  and
  r2.headers == MutableHeaders({'content-length': '4', 'content-type': 'application/json'}))

Debugging material

All that is needed is the currently stable release of Starlette, and the trusty terminal.

Environment

  • OS: same on all of Linux/Windows/macOS
  • Python version: 3.7.x
  • Starlette version: 0.14.x

Additional context

I was trying to adhere to the HTTP ref spec when constructing responses to the OPTIONS and HEAD methods.

@accredited-unicorn
Copy link
Author

Nota Bene

While the bug still persists, there is a work around for the particular use case which prompted this bug report ...

Implement the patch for an empty response class, from pull request #1270, directly in your own backend code. Then

return EmptyResponse(headers={'Allow': '<comma separated list of allowed HTTP methods>'}, status_code=204)

@iamthen0ise
Copy link

iamthen0ise commented Sep 7, 2021

@accredited-unicorn Hi!

Looks like I have a suggestion why this is happens.

When you pass just quotes ('') into JSONResponse, then length is counting against the quotes, and 2 looks like reasonable value.

But most interesting part is — Why it is 4 when you pass None or even do not specify value?

Let's take a look at the code:

Here is rendering of empty JSON Response

def render(self, content: typing.Any) -> bytes:
return json.dumps(
content,
ensure_ascii=False,
allow_nan=False,
indent=None,
separators=(",", ":"),
).encode("utf-8")

Looks good, but what happens if we try to encode None into UTF-8 with json.dumps? Let's try in your favourite REPL:

In [3]: import json

In [4]: json.dumps(None).encode("utf-8")
Out[4]: 'null'

That's why empty response weights 4.

Personally I do not even sure this is a bug. I suggest such behaviour as expected when you deal with JSON.
Here's good related case at StackOverflow https://stackoverflow.com/questions/3548635/python-json-what-happened-to-none
And one more https://stackoverflow.com/questions/11410896/python-how-json-dumps-none-to-empty-string

So, json.dumps must rewrite None to valid null, that's why it happens.

@declaresub
Copy link

Nota Bene

While the bug still persists, there is a work around for the particular use case which prompted this bug report ...

Implement the patch for an empty response class, from pull request #1270, directly in your own backend code. Then

return EmptyResponse(headers={'Allow': '<comma separated list of allowed HTTP methods>'}, status_code=204)

I have just revised that pull request. I eliminated the EmptyResponse class in favor of handling the special cases in the Response class. The unit tests I wrote also check that JSONResponse does the right thing for 204, 205, and 304 status codes.

@tomchristie
Copy link
Member

If you want to return an empty response, then return Response().

Or even better, once #1397 is in, then return Response(status_code=204) which is a more semantically correct way to return no-content responses.

The behaviour we currently have does make sense...

  • Returning JSONResponse(None) will set the body as json.dumps(None) -> null.
  • Returning JSONResponse('') will set the body as json.dumps('') -> "".

Including an application/json content type, but not including any body with the response would be invalid.

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 a pull request may close this issue.

4 participants