From e6f1a5faca19728e6f5274c9160ff4b331bbc7b4 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 5 Oct 2020 16:57:11 +0200 Subject: [PATCH 1/2] Add deflate compression algorithm --- README.md | 8 ++++---- flask_compress.py | 7 +++++-- setup.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 08456d4..97ac86d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Coverage](https://coveralls.io/repos/libwilliam/flask-compress/badge.svg)](https://coveralls.io/github/libwilliam/flask-compress) [![License](https://img.shields.io/pypi/l/flask-compress.svg)](https://github.com/libwilliam/flask-compress/blob/master/LICENSE.txt) -Flask-Compress allows you to easily compress your [Flask](http://flask.pocoo.org/) application's responses with gzip. +Flask-Compress allows you to easily compress your [Flask](http://flask.pocoo.org/) application's responses with gzip, deflate or brotli. The preferred solution is to have a server (like [Nginx](http://wiki.nginx.org/Main)) automatically compress the static files for you. If you don't have that option Flask-Compress will solve the problem for you. @@ -49,7 +49,7 @@ $ easy_install flask-compress ## Using Flask-Compress -Flask-Compress is incredibly simple to use. In order to start gzip'ing your Flask application's assets, the first thing to do is let Flask-Compress know about your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) application object. +Flask-Compress is incredibly simple to use. In order to start compressing your Flask application's assets, the first thing to do is let Flask-Compress know about your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) application object. ```python from flask import Flask @@ -73,7 +73,7 @@ def start_app(): return app ``` -In terms of automatically compressing your assets using gzip, passing your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) object to the `flask_compress.Compress` object is all that needs to be done. +In terms of automatically compressing your assets, passing your [`flask.Flask`](http://flask.pocoo.org/docs/latest/api/#flask.Flask) object to the `flask_compress.Compress` object is all that needs to be done. ## Options @@ -88,7 +88,7 @@ Within your Flask application's settings you can provide the following settings | `COMPRESS_CACHE_KEY` | Specifies the cache key method for lookup/storage of response data. | `None` | | `COMPRESS_CACHE_BACKEND` | Specified the backend for storing the cached response data. | `None` | | `COMPRESS_REGISTER` | Specifies if compression should be automatically registered. | `True` | -| `COMPRESS_ALGORITHM` | Supported compression algorithms. | `['br', 'gzip']` | +| `COMPRESS_ALGORITHM` | Supported compression algorithms. | `['br', 'gzip', 'deflate']` | | `COMPRESS_BR_MODE` | For Brotli, the compression mode. The options are 0, 1, or 2. These correspond to "generic", "text" (for UTF-8 input), and "font" (for WOFF 2.0). | `0` | | `COMPRESS_BR_QUALITY` | For Brotli, the desired compression level. Proivdes control over the speed/compression density tradeoff. Higher values provide better compression at the cost of compression time. Ranges from 0 to 11. | `4` | | `COMPRESS_BR_WINDOW` | For Brotli, this specifies the base-2 logarithm of the sliding window size. Ranges from 10 to 24. | `22` | diff --git a/flask_compress.py b/flask_compress.py index 65bb0ef..31d0b40 100644 --- a/flask_compress.py +++ b/flask_compress.py @@ -5,6 +5,7 @@ import sys from gzip import GzipFile +import zlib from io import BytesIO from collections import defaultdict @@ -74,7 +75,7 @@ def init_app(self, app): ('COMPRESS_CACHE_KEY', None), ('COMPRESS_CACHE_BACKEND', None), ('COMPRESS_REGISTER', True), - ('COMPRESS_ALGORITHM', ['br', 'gzip']), + ('COMPRESS_ALGORITHM', ['br', 'gzip', 'deflate']), ] for k, v in defaults: @@ -102,7 +103,7 @@ def _choose_compress_algorithm(self, accept_encoding_header): means the client prefers that algorithm more). :param accept_encoding_header: Content of the `Accept-Encoding` header - :return: Name of a compression algorithm (e.g. `gzip` or `br`) or `None` if + :return: name of a compression algorithm (`gzip`, `deflate`, `br`) or `None` if the client and server don't agree on any. """ # Map quality factors to requested algorithm names. @@ -202,6 +203,8 @@ def compress(self, app, response, algorithm): fileobj=gzip_buffer) as gzip_file: gzip_file.write(response.get_data()) return gzip_buffer.getvalue() + elif algorithm == 'deflate': + return zlib.compress(response.get_data()) elif algorithm == 'br': return brotli.compress(response.get_data(), mode=app.config['COMPRESS_BR_MODE'], diff --git a/setup.py b/setup.py index e1b8bb0..b5a0c8a 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ license='MIT', author='Thomas Mansencal', author_email='thomas.mansencal@gmail.com', - description='Compress responses in your Flask app with gzip or brotli.', + description='Compress responses in your Flask app with gzip, deflate or brotli.', long_description=LONG_DESCRIPTION, long_description_content_type='text/markdown', py_modules=['flask_compress'], From 9af66b97b5bf3402f05b1133c63e4d0b1ae87953 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 7 Oct 2020 08:42:43 +0200 Subject: [PATCH 2/2] Add support for deflate compression level --- README.md | 1 + flask_compress.py | 4 +++- tests/test_flask_compress.py | 21 ++++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 97ac86d..37f31b4 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Within your Flask application's settings you can provide the following settings | ------ | ----------- | ------- | | `COMPRESS_MIMETYPES` | Set the list of mimetypes to compress here. | `[`
`'text/html',`
`'text/css',`
`'text/xml',`
`'application/json',`
`'application/javascript'`
`]` | | `COMPRESS_LEVEL` | Specifies the gzip compression level. | `6` | +| `COMPRESS_DEFLATE_LEVEL` | Specifies the deflate compression level. | `-1` | | `COMPRESS_MIN_SIZE` | Specifies the minimum file size threshold for compressing files. | `500` | | `COMPRESS_CACHE_KEY` | Specifies the cache key method for lookup/storage of response data. | `None` | | `COMPRESS_CACHE_BACKEND` | Specified the backend for storing the cached response data. | `None` | diff --git a/flask_compress.py b/flask_compress.py index 31d0b40..6a337dc 100644 --- a/flask_compress.py +++ b/flask_compress.py @@ -71,6 +71,7 @@ def init_app(self, app): ('COMPRESS_BR_QUALITY', 4), ('COMPRESS_BR_WINDOW', 22), ('COMPRESS_BR_BLOCK', 0), + ('COMPRESS_DEFLATE_LEVEL', -1), ('COMPRESS_MIN_SIZE', 500), ('COMPRESS_CACHE_KEY', None), ('COMPRESS_CACHE_BACKEND', None), @@ -204,7 +205,8 @@ def compress(self, app, response, algorithm): gzip_file.write(response.get_data()) return gzip_buffer.getvalue() elif algorithm == 'deflate': - return zlib.compress(response.get_data()) + return zlib.compress(response.get_data(), + app.config['COMPRESS_DEFLATE_LEVEL']) elif algorithm == 'br': return brotli.compress(response.get_data(), mode=app.config['COMPRESS_BR_MODE'], diff --git a/tests/test_flask_compress.py b/tests/test_flask_compress.py index ab9cc93..eff7920 100644 --- a/tests/test_flask_compress.py +++ b/tests/test_flask_compress.py @@ -29,7 +29,11 @@ def test_min_size_default(self): def test_algorithm_default(self): """ Tests COMPRESS_ALGORITHM default value is correctly set. """ - self.assertEqual(self.app.config['COMPRESS_ALGORITHM'], ['br', 'gzip']) + self.assertEqual(self.app.config['COMPRESS_ALGORITHM'], ['br', 'gzip', 'deflate']) + + def test_default_deflate_settings(self): + """ Tests COMPRESS_DELATE_LEVEL default value is correctly set. """ + self.assertEqual(self.app.config['COMPRESS_DEFLATE_LEVEL'], -1) def test_mode_default(self): """ Tests COMPRESS_BR_MODE default value is correctly set. """ @@ -145,6 +149,21 @@ def test_quality_level(self): self.assertNotEqual(response4_size, response11_size) + def test_deflate_compression_level(self): + """ Tests COMPRESS_DELATE_LEVEL correctly affects response data. """ + self.app.config['COMPRESS_DEFLATE_LEVEL'] = -1 + client = self.app.test_client() + response = client.get('/large/', headers=[('Accept-Encoding', 'deflate')]) + response_size = len(response.data) + + self.app.config['COMPRESS_DEFLATE_LEVEL'] = 1 + client = self.app.test_client() + response = client.get('/large/', headers=[('Accept-Encoding', 'deflate')]) + response1_size = len(response.data) + + self.assertNotEqual(response_size, response1_size) + + class CompressionAlgoTests(unittest.TestCase): """ Test different scenarios for compression algorithm negotiation between