From 77d293cf49e586f03fbea96d0bae237bc7ed230f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 1 Jun 2013 19:24:03 +0100 Subject: [PATCH] Order JSON keys by default to avoid trashing HTTP caches --- CHANGES | 2 ++ docs/config.rst | 13 ++++++++++++- flask/app.py | 1 + flask/json.py | 2 ++ flask/testsuite/helpers.py | 40 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7be9c77a..378488f6 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,8 @@ Release date to be decided. more reliable in case something handles an exception halfway through the error handling process. - Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable. +- Flask now orders JSON keys by default to not trash HTTP caches due to + different hash seeds between different workers. Version 0.9 ----------- diff --git a/docs/config.rst b/docs/config.rst index 0dbb71d7..ced2ad82 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -149,6 +149,17 @@ The following configuration values are used internally by Flask: unicode strings. ``jsonfiy`` will automatically encode it in ``utf-8`` then for transport for instance. +``JSON_SORT_KEYS`` By default Flask will serialize JSON + objects in a way that the keys are + ordered. This is done in order to + ensure that independent of the hash seed + of the dictionary the return value will + be consistent to not trash external HTTP + caches. You can override the default + behavior by changing this variable. + This is not recommended but might give + you a performance improvement on the + cost of cachability. ``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) jsonify responses will be pretty printed if they are not requested by an @@ -197,7 +208,7 @@ The following configuration values are used internally by Flask: ``PREFERRED_URL_SCHEME`` .. versionadded:: 0.10 - ``JSON_AS_ASCII``, ``JSONIFY_PRETTYPRINT_REGULAR`` + ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 86f2a212..7b286571 100644 --- a/flask/app.py +++ b/flask/app.py @@ -291,6 +291,7 @@ class Flask(_PackageBoundObject): 'TRAP_HTTP_EXCEPTIONS': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, + 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, }) diff --git a/flask/json.py b/flask/json.py index e43c4ed9..875af67e 100644 --- a/flask/json.py +++ b/flask/json.py @@ -92,10 +92,12 @@ class JSONDecoder(_json.JSONDecoder): def _dump_arg_defaults(kwargs): """Inject default arguments for dump functions.""" + kwargs.setdefault('sort_keys', True) if current_app: kwargs.setdefault('cls', current_app.json_encoder) if not current_app.config['JSON_AS_ASCII']: kwargs.setdefault('ensure_ascii', False) + kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS']) def _load_arg_defaults(kwargs): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 7750ae52..c88c6241 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -148,6 +148,46 @@ class JSONTestCase(FlaskTestCase): if not has_encoding('euc-kr'): test_modified_url_encoding = None + def test_json_key_sorting(self): + app = flask.Flask(__name__) + app.testing = True + self.assert_equal(app.config['JSON_SORT_KEYS'], True) + d = dict.fromkeys(range(20), 'foo') + + @app.route('/') + def index(): + return flask.jsonify(values=d) + + c = app.test_client() + rv = c.get('/') + lines = [x.strip() for x in rv.data.strip().decode('utf-8').splitlines()] + self.assert_equal(lines, [ + '{', + '"values": {', + '"0": "foo",', + '"1": "foo",', + '"2": "foo",', + '"3": "foo",', + '"4": "foo",', + '"5": "foo",', + '"6": "foo",', + '"7": "foo",', + '"8": "foo",', + '"9": "foo",', + '"10": "foo",', + '"11": "foo",', + '"12": "foo",', + '"13": "foo",', + '"14": "foo",', + '"15": "foo",', + '"16": "foo",', + '"17": "foo",', + '"18": "foo",', + '"19": "foo"', + '}', + '}' + ]) + class SendfileTestCase(FlaskTestCase):