fix content length mismatch in windows and other platform

The current implementation of `sanic` attempts to make use of `ujson` if
it's available in the system and if not, it will default to the inbuilt
`json` module provided by python.

The current implementation of `ujson` does not provide a mechanism to
provide a custom `seperators` parameter as part of the `dumps` method
invocation and the default behavior of the module is to strip all the
spaces around seperators such as `:` and `,`. This leads to an
inconsistency in the response length when the response is generated
using the `ujson` and in built `json` module provided by python.

To maintain the consistency, this commit overrides the default behavior
of the `dumps` method provided by the `json` module to add a `seperators`
argument that will strip the white spaces around these character like
the default behavior of `ujson`

This addresses the issue referenced in #1398

Signed-off-by: Harsha Narayana <harsha2k4@gmail.com>
This commit is contained in:
Harsha Narayana 2018-11-07 21:32:34 +05:30
parent 92b73a6f4f
commit d4d1df03c9
No known key found for this signature in database
GPG Key ID: 8AF211CB60D4B28C
2 changed files with 12 additions and 13 deletions

View File

@ -1,3 +1,4 @@
from functools import partial
from mimetypes import guess_type from mimetypes import guess_type
from os import path from os import path
from urllib.parse import quote_plus from urllib.parse import quote_plus
@ -12,7 +13,11 @@ from sanic.helpers import STATUS_CODES, has_message_body, remove_entity_headers
try: try:
from ujson import dumps as json_dumps from ujson import dumps as json_dumps
except BaseException: except BaseException:
from json import dumps as json_dumps from json import dumps
# This is done in order to ensure that the JSON response is
# kept consistent across both ujson and inbuilt json usage.
json_dumps = partial(dumps, separators=(",", ":"))
class BaseHTTPResponse: class BaseHTTPResponse:

View File

@ -1,20 +1,19 @@
import sys
import asyncio import asyncio
import inspect import inspect
import os import os
from aiofiles import os as async_os
from mimetypes import guess_type from mimetypes import guess_type
from random import choice
from unittest.mock import MagicMock
from urllib.parse import unquote from urllib.parse import unquote
import pytest import pytest
from random import choice from aiofiles import os as async_os
from sanic.response import ( from sanic.response import (
HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json
) )
from sanic.server import HttpProtocol from sanic.server import HttpProtocol
from sanic.testing import HOST, PORT from sanic.testing import HOST, PORT
from unittest.mock import MagicMock
JSON_DATA = {'ok': True} JSON_DATA = {'ok': True}
@ -38,7 +37,6 @@ async def sample_streaming_fn(response):
def test_method_not_allowed(app): def test_method_not_allowed(app):
@app.get('/') @app.get('/')
async def test_get(request): async def test_get(request):
return response.json({'hello': 'world'}) return response.json({'hello': 'world'})
@ -55,12 +53,12 @@ def test_method_not_allowed(app):
request, response = app.test_client.head('/') request, response = app.test_client.head('/')
assert response.status == 405 assert response.status == 405
assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST']) assert set(response.headers['Allow'].split(', ')) == {'GET', 'POST'}
assert response.headers['Content-Length'] == '0' assert response.headers['Content-Length'] == '0'
request, response = app.test_client.patch('/') request, response = app.test_client.patch('/')
assert response.status == 405 assert response.status == 405
assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST']) assert set(response.headers['Allow'].split(', ')) == {'GET', 'POST'}
assert response.headers['Content-Length'] == '0' assert response.headers['Content-Length'] == '0'
@ -74,15 +72,11 @@ def test_response_header(app):
'CONTENT-TYPE': 'application/json' 'CONTENT-TYPE': 'application/json'
}) })
is_windows = sys.platform in ['win32', 'cygwin']
request, response = app.test_client.get('/') request, response = app.test_client.get('/')
assert dict(response.headers) == { assert dict(response.headers) == {
'Connection': 'keep-alive', 'Connection': 'keep-alive',
'Keep-Alive': str(app.config.KEEP_ALIVE_TIMEOUT), 'Keep-Alive': str(app.config.KEEP_ALIVE_TIMEOUT),
# response body contains an extra \r at the end if its windows 'Content-Length': '11',
# TODO: this is the only place this difference shows up in our tests
# we should figure out a way to unify testing on both platforms
'Content-Length': '12' if is_windows else '11',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }