From b2bec66dc9233cf1020ea2997244ef4b34c866c3 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 5 Oct 2015 12:17:52 +0100 Subject: [PATCH 1/3] Add a Brotli endpoint. --- httpbin/core.py | 9 +++++++++ httpbin/filters.py | 25 +++++++++++++++++++++++++ setup.py | 4 +++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/httpbin/core.py b/httpbin/core.py index 50e75249..446b581d 100644 --- a/httpbin/core.py +++ b/httpbin/core.py @@ -204,6 +204,15 @@ def view_deflate_encoded_content(): 'origin', 'headers', method=request.method, deflated=True)) +@app.route('/brotli') +@filters.brotli +def view_brotli_encoded_content(): + """Returns Brotli-Encoded Data.""" + + return jsonify(get_dict( + 'origin', 'headers', method=request.method, brotli=True)) + + @app.route('/redirect/') def redirect_n_times(n): """302 Redirects n times.""" diff --git a/httpbin/filters.py b/httpbin/filters.py index e18382c2..6bfd2fe9 100644 --- a/httpbin/filters.py +++ b/httpbin/filters.py @@ -10,6 +10,8 @@ import gzip as gzip2 import zlib +import brotli as _brotli + from six import BytesIO from decimal import Decimal from time import time as now @@ -88,3 +90,26 @@ def deflate(f, *args, **kwargs): return data return deflated_data + + +@decorator +def brotli(f, *args, **kwargs): + """Brotli Flask Response Decorator""" + + data = f(*args, **kwargs) + + if isinstance(data, Response): + content = data.data + else: + content = data + + deflated_data = _brotli.compress(content) + + if isinstance(data, Response): + data.data = deflated_data + data.headers['Content-Encoding'] = 'brotli' + data.headers['Content-Length'] = str(len(data.data)) + + return data + + return deflated_data diff --git a/setup.py b/setup.py index 7c702318..857c35a9 100644 --- a/setup.py +++ b/setup.py @@ -32,5 +32,7 @@ ], packages=find_packages(), include_package_data = True, # include files listed in MANIFEST.in - install_requires=['Flask','MarkupSafe','decorator','itsdangerous','six'], + install_requires=[ + 'Flask','MarkupSafe','decorator','itsdangerous','six','brotlipy' + ], ) From dfed4ad63511686d51aac26bbd751cffe37ee1f5 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 5 Oct 2015 12:22:37 +0100 Subject: [PATCH 2/3] Add basic do-almost-nothing Brotli endpoint test --- test_httpbin.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test_httpbin.py b/test_httpbin.py index 920d3876..f9f666ee 100755 --- a/test_httpbin.py +++ b/test_httpbin.py @@ -236,6 +236,10 @@ def test_gzip(self): response = self.app.get('/gzip') self.assertEqual(response.status_code, 200) + def test_brotli(self): + response = self.app.get('/brotli') + self.assertEqual(response.status_code, 200) + def test_digest_auth_with_wrong_password(self): auth_header = 'Digest username="user",realm="wrong",nonce="wrong",uri="/digest-auth/user/passwd/MD5",response="wrong",opaque="wrong"' response = self.app.get( @@ -488,7 +492,7 @@ def test_request_range_first_15_bytes(self): self.assertEqual(response.headers.get('ETag'), 'range1000') self.assertEqual(self.get_data(response), 'abcdefghijklmnop'.encode('utf8')) self.assertEqual(response.headers.get('Content-range'), 'bytes 0-15/1000') - + def test_request_range_open_ended_last_6_bytes(self): response = self.app.get( '/range/26', @@ -499,7 +503,7 @@ def test_request_range_open_ended_last_6_bytes(self): self.assertEqual(response.headers.get('ETag'), 'range26') self.assertEqual(self.get_data(response), 'uvwxyz'.encode('utf8')) self.assertEqual(response.headers.get('Content-range'), 'bytes 20-25/26') - + def test_request_range_suffix(self): response = self.app.get( '/range/26', @@ -510,7 +514,7 @@ def test_request_range_suffix(self): self.assertEqual(response.headers.get('ETag'), 'range26') self.assertEqual(self.get_data(response), 'vwxyz'.encode('utf8')) self.assertEqual(response.headers.get('Content-range'), 'bytes 21-25/26') - + def test_request_out_of_bounds(self): response = self.app.get( '/range/26', @@ -522,13 +526,13 @@ def test_request_out_of_bounds(self): self.assertEqual(response.headers.get('ETag'), 'range26') self.assertEqual(len(self.get_data(response)), 0) self.assertEqual(response.headers.get('Content-range'), 'bytes */26') - + response = self.app.get( '/range/26', headers={ 'Range': 'bytes=32-40', } ) - + self.assertEqual(response.status_code, 416) response = self.app.get( '/range/26', From c916b2197e049f13f939c77b8d3d1b1179a36442 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 7 Oct 2015 09:50:14 +0100 Subject: [PATCH 3/3] Brotli content-encoding renamed to 'br'. See https://bugzilla.mozilla.org/show_bug.cgi?id=1211916 --- httpbin/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpbin/filters.py b/httpbin/filters.py index 6bfd2fe9..c6268b6a 100644 --- a/httpbin/filters.py +++ b/httpbin/filters.py @@ -107,7 +107,7 @@ def brotli(f, *args, **kwargs): if isinstance(data, Response): data.data = deflated_data - data.headers['Content-Encoding'] = 'brotli' + data.headers['Content-Encoding'] = 'br' data.headers['Content-Length'] = str(len(data.data)) return data