From 041f73462cc30078e5d353c4840012e53b5b89ea Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 20:58:14 +1000 Subject: [PATCH 01/15] Refactored CompressEncoder to support multiple methods in a single middleware, with backward compat --- composer.json | 4 ++ src/BrotliCompressor.php | 30 ++++++++++++ src/{Encoder.php => CompressEncoder.php} | 58 +++++++++++++++++++----- src/CompressorInterface.php | 21 +++++++++ src/DeflateCompressor.php | 29 ++++++++++++ src/DeflateEncoder.php | 15 ++---- src/GzipCompressor.php | 29 ++++++++++++ src/GzipEncoder.php | 15 ++---- src/ZStdCompressor.php | 30 ++++++++++++ tests/EncoderTest.php | 1 - 10 files changed, 198 insertions(+), 34 deletions(-) create mode 100644 src/BrotliCompressor.php rename src/{Encoder.php => CompressEncoder.php} (60%) create mode 100644 src/CompressorInterface.php create mode 100644 src/DeflateCompressor.php create mode 100644 src/GzipCompressor.php create mode 100644 src/ZStdCompressor.php diff --git a/composer.json b/composer.json index 3cb95fd..ab91059 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,10 @@ "oscarotero/php-cs-fixer-config": "^1.0", "phpstan/phpstan": "^0.12" }, + "suggest": { + "ext-brotli": "Enable Brotli Compression", + "ext-zstd": "Enable ZStd Compression" + }, "autoload": { "psr-4": { "Middlewares\\": "src/" diff --git a/src/BrotliCompressor.php b/src/BrotliCompressor.php new file mode 100644 index 0000000..e98efab --- /dev/null +++ b/src/BrotliCompressor.php @@ -0,0 +1,30 @@ +level = $level; + } + + public function name(): string + { + return 'br'; + } + + public function compress(string $input): string + { + return brotli_compress($input, $this->level); + } +} diff --git a/src/Encoder.php b/src/CompressEncoder.php similarity index 60% rename from src/Encoder.php rename to src/CompressEncoder.php index 69269d6..a665f44 100644 --- a/src/Encoder.php +++ b/src/CompressEncoder.php @@ -4,12 +4,13 @@ namespace Middlewares; use Middlewares\Utils\Factory; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\RequestHandlerInterface; -abstract class Encoder +class CompressEncoder { /** * @var string @@ -26,9 +27,20 @@ abstract class Encoder */ private $patterns = ['/^(image\/svg\\+xml|text\/.*|application\/json)(;.*)?$/']; - public function __construct(StreamFactoryInterface $streamFactory = null) + /** + * @var CompressorInterface[] + */ + private $compressors; + + /** + * CompressEncoder constructor. + * @param StreamFactoryInterface|null $streamFactory + * @param CompressorInterface[]|null $compressors + */ + public function __construct(StreamFactoryInterface $streamFactory = null, array $compressors = null) { $this->streamFactory = $streamFactory ?: Factory::getStreamFactory(); + $this->compressors = $compressors ?: $this->allAvailableCompressors(); } /** @@ -38,11 +50,13 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface { $response = $handler->handle($request); - if (stripos($request->getHeaderLine('Accept-Encoding'), $this->encoding) !== false - && !$response->hasHeader('Content-Encoding') + $compressor = $this->getCompressor($request); + if ( + !$response->hasHeader('Content-Encoding') + && $compressor && $this->isCompressible($response) ) { - $stream = $this->streamFactory->createStream($this->encode((string) $response->getBody())); + $stream = $this->streamFactory->createStream($compressor->compress((string) $response->getBody())); $vary = array_filter(array_map('trim', explode(',', $response->getHeaderLine('Vary')))); if (!in_array('Accept-Encoding', $vary, true)) { @@ -50,7 +64,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } return $response - ->withHeader('Content-Encoding', $this->encoding) + ->withHeader('Content-Encoding', $compressor->name()) ->withHeader('Vary', implode(',', $vary)) ->withBody($stream); } @@ -58,11 +72,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $response; } - /** - * Encode the body content. - */ - abstract protected function encode(string $content): string; - /** * Sets the list of compressible content-type patterns. * If pattern begins with '/' treat as regular expression @@ -73,6 +82,17 @@ public function contentType(string ...$patterns): self return $this; } + private function getCompressor(RequestInterface $request): ?CompressorInterface + { + $acceptEncoding = $request->getHeaderLine('Accept-Encoding'); + foreach ($this->compressors as $comp) { + if (stripos($acceptEncoding, $comp->name()) !== false) { + return $comp; + } + } + return null; + } + private function isCompressible(ResponseInterface $response): bool { $contentType = $response->getHeaderLine('Content-Type') ?: 'text/html'; @@ -89,4 +109,20 @@ private function isCompressible(ResponseInterface $response): bool } return false; } + + /** + * @return CompressorInterface[] + */ + private function allAvailableCompressors(): array + { + $o = []; + if (function_exists('zstd_compress')) { + $o[] = new ZStdCompressor(); + } + if (function_exists('brotli_compress')) { + $o[] = new BrotliCompressor(); + } + $o[] = new GzipCompressor(); + return $o; + } } diff --git a/src/CompressorInterface.php b/src/CompressorInterface.php new file mode 100644 index 0000000..8ee02bb --- /dev/null +++ b/src/CompressorInterface.php @@ -0,0 +1,21 @@ +level = $level; + } + + public function name(): string + { + return 'deflate'; + } + + public function compress(string $input): string + { + return gzdeflate($input, $this->level); + } +} diff --git a/src/DeflateEncoder.php b/src/DeflateEncoder.php index f63bd7c..0f9122f 100644 --- a/src/DeflateEncoder.php +++ b/src/DeflateEncoder.php @@ -3,20 +3,13 @@ namespace Middlewares; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; -class DeflateEncoder extends Encoder implements MiddlewareInterface +class DeflateEncoder extends CompressEncoder implements MiddlewareInterface { - /** - * @var string - */ - protected $encoding = 'deflate'; - - /** - * {@inheritdoc} - */ - protected function encode(string $content): string + public function __construct(StreamFactoryInterface $streamFactory = null) { - return (string) gzdeflate($content); + parent::__construct($streamFactory, [new DeflateCompressor()]); } } diff --git a/src/GzipCompressor.php b/src/GzipCompressor.php new file mode 100644 index 0000000..c37332a --- /dev/null +++ b/src/GzipCompressor.php @@ -0,0 +1,29 @@ +level = $level; + } + + public function name(): string + { + return 'gzip'; + } + + public function compress(string $input): string + { + return gzencode($input, $this->level); + } +} diff --git a/src/GzipEncoder.php b/src/GzipEncoder.php index dd7724f..dc0e06e 100644 --- a/src/GzipEncoder.php +++ b/src/GzipEncoder.php @@ -3,20 +3,13 @@ namespace Middlewares; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; -class GzipEncoder extends Encoder implements MiddlewareInterface +class GzipEncoder extends CompressEncoder implements MiddlewareInterface { - /** - * @var string - */ - protected $encoding = 'gzip'; - - /** - * {@inheritdoc} - */ - protected function encode(string $content): string + public function __construct(StreamFactoryInterface $streamFactory = null) { - return (string) gzencode($content); + parent::__construct($streamFactory, [new GzipCompressor()]); } } diff --git a/src/ZStdCompressor.php b/src/ZStdCompressor.php new file mode 100644 index 0000000..8af7c76 --- /dev/null +++ b/src/ZStdCompressor.php @@ -0,0 +1,30 @@ +level = $level; + } + + public function name(): string + { + return 'zstd'; + } + + public function compress(string $input): string + { + return zstd_compress($input, $this->level); + } +} diff --git a/tests/EncoderTest.php b/tests/EncoderTest.php index b8e9756..c06032d 100644 --- a/tests/EncoderTest.php +++ b/tests/EncoderTest.php @@ -123,7 +123,6 @@ function () { return self::makeResponse('text/html', 'html'); }, ], $request); - $this->assertEquals( true, $response->hasHeader('Content-Encoding'), From 4d06533ebbd30e336c8f5505b6bc9af20fc97bbd Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 21:04:19 +1000 Subject: [PATCH 02/15] reformat: code style for phpcs --- src/CompressEncoder.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/CompressEncoder.php b/src/CompressEncoder.php index a665f44..7cc244a 100644 --- a/src/CompressEncoder.php +++ b/src/CompressEncoder.php @@ -51,9 +51,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = $handler->handle($request); $compressor = $this->getCompressor($request); - if ( - !$response->hasHeader('Content-Encoding') - && $compressor + if ($compressor + && !$response->hasHeader('Content-Encoding') && $this->isCompressible($response) ) { $stream = $this->streamFactory->createStream($compressor->compress((string) $response->getBody())); From 45ea5b9fbd5f5e69c0493c47c9dc568c4c8aae0c Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 21:23:14 +1000 Subject: [PATCH 03/15] fixed some phpstan warnings about return types --- src/BrotliCompressor.php | 6 +++++- src/DeflateCompressor.php | 6 +++++- src/GzipCompressor.php | 6 +++++- src/ZStdCompressor.php | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/BrotliCompressor.php b/src/BrotliCompressor.php index e98efab..dd2f248 100644 --- a/src/BrotliCompressor.php +++ b/src/BrotliCompressor.php @@ -25,6 +25,10 @@ public function name(): string public function compress(string $input): string { - return brotli_compress($input, $this->level); + $out = brotli_compress($input, $this->level); + if($out === false) { + throw new \RuntimeException('Error occurred while compressing output'); + } + return $out; } } diff --git a/src/DeflateCompressor.php b/src/DeflateCompressor.php index a44f27f..130eae8 100644 --- a/src/DeflateCompressor.php +++ b/src/DeflateCompressor.php @@ -24,6 +24,10 @@ public function name(): string public function compress(string $input): string { - return gzdeflate($input, $this->level); + $out = gzdeflate($input, $this->level); + if($out === false) { + throw new \RuntimeException('Error occurred while compressing output'); + } + return $out; } } diff --git a/src/GzipCompressor.php b/src/GzipCompressor.php index c37332a..fa6ecff 100644 --- a/src/GzipCompressor.php +++ b/src/GzipCompressor.php @@ -24,6 +24,10 @@ public function name(): string public function compress(string $input): string { - return gzencode($input, $this->level); + $out = gzencode($input, $this->level); + if($out === false) { + throw new \RuntimeException('Error occurred while compressing output'); + } + return $out; } } diff --git a/src/ZStdCompressor.php b/src/ZStdCompressor.php index 8af7c76..1a6a8a4 100644 --- a/src/ZStdCompressor.php +++ b/src/ZStdCompressor.php @@ -25,6 +25,10 @@ public function name(): string public function compress(string $input): string { - return zstd_compress($input, $this->level); + $out = zstd_compress($input, $this->level); + if($out === false) { + throw new \RuntimeException('Error occurred while compressing output'); + } + return $out; } } From ff62d58da6bc58fbf7186d568ee457c97b94fd00 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 21:39:30 +1000 Subject: [PATCH 04/15] setting brotli default compression to a much more sensible value --- src/BrotliCompressor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BrotliCompressor.php b/src/BrotliCompressor.php index dd2f248..49a603f 100644 --- a/src/BrotliCompressor.php +++ b/src/BrotliCompressor.php @@ -11,9 +11,9 @@ final class BrotliCompressor implements CompressorInterface * Brotli Compression Support (requires brotli php extension) * * @link https://github.com/kjdev/php-ext-brotli - * @param int $level Brotli Compression Level, 11 is default + * @param int $level Brotli Compression Level, 5 is default (small than gzip, about as fast) */ - public function __construct(int $level = 11) + public function __construct(int $level = 5) { $this->level = $level; } From f08107506e188248c0b3f0b2a4f4588363bef6c2 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 21:41:58 +1000 Subject: [PATCH 05/15] reformat: fix if bracket spacing --- src/BrotliCompressor.php | 2 +- src/DeflateCompressor.php | 2 +- src/GzipCompressor.php | 2 +- src/ZStdCompressor.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BrotliCompressor.php b/src/BrotliCompressor.php index 49a603f..0dc525a 100644 --- a/src/BrotliCompressor.php +++ b/src/BrotliCompressor.php @@ -26,7 +26,7 @@ public function name(): string public function compress(string $input): string { $out = brotli_compress($input, $this->level); - if($out === false) { + if ($out === false) { throw new \RuntimeException('Error occurred while compressing output'); } return $out; diff --git a/src/DeflateCompressor.php b/src/DeflateCompressor.php index 130eae8..868d223 100644 --- a/src/DeflateCompressor.php +++ b/src/DeflateCompressor.php @@ -25,7 +25,7 @@ public function name(): string public function compress(string $input): string { $out = gzdeflate($input, $this->level); - if($out === false) { + if ($out === false) { throw new \RuntimeException('Error occurred while compressing output'); } return $out; diff --git a/src/GzipCompressor.php b/src/GzipCompressor.php index fa6ecff..85531ac 100644 --- a/src/GzipCompressor.php +++ b/src/GzipCompressor.php @@ -25,7 +25,7 @@ public function name(): string public function compress(string $input): string { $out = gzencode($input, $this->level); - if($out === false) { + if ($out === false) { throw new \RuntimeException('Error occurred while compressing output'); } return $out; diff --git a/src/ZStdCompressor.php b/src/ZStdCompressor.php index 1a6a8a4..9c244c0 100644 --- a/src/ZStdCompressor.php +++ b/src/ZStdCompressor.php @@ -26,7 +26,7 @@ public function name(): string public function compress(string $input): string { $out = zstd_compress($input, $this->level); - if($out === false) { + if ($out === false) { throw new \RuntimeException('Error occurred while compressing output'); } return $out; From 4c20263efe66ae166fa7b819a3064c3a970c23ad Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 22:02:24 +1000 Subject: [PATCH 06/15] remove unused code, and defined namespaces explicitly --- composer.json | 3 ++- src/BrotliCompressor.php | 2 +- src/CompressEncoder.php | 5 ----- src/ZStdCompressor.php | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ab91059..8d0f3e6 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "friendsofphp/php-cs-fixer": "^2.0", "squizlabs/php_codesniffer": "^3.0", "oscarotero/php-cs-fixer-config": "^1.0", - "phpstan/phpstan": "^0.12" + "phpstan/phpstan": "^0.12", + "vimeo/psalm": "^4.8" }, "suggest": { "ext-brotli": "Enable Brotli Compression", diff --git a/src/BrotliCompressor.php b/src/BrotliCompressor.php index 0dc525a..f9fa895 100644 --- a/src/BrotliCompressor.php +++ b/src/BrotliCompressor.php @@ -25,7 +25,7 @@ public function name(): string public function compress(string $input): string { - $out = brotli_compress($input, $this->level); + $out = \brotli_compress($input, $this->level); if ($out === false) { throw new \RuntimeException('Error occurred while compressing output'); } diff --git a/src/CompressEncoder.php b/src/CompressEncoder.php index 7cc244a..3b9ca5d 100644 --- a/src/CompressEncoder.php +++ b/src/CompressEncoder.php @@ -12,11 +12,6 @@ class CompressEncoder { - /** - * @var string - */ - protected $encoding; - /** * @var StreamFactoryInterface */ diff --git a/src/ZStdCompressor.php b/src/ZStdCompressor.php index 9c244c0..0d6f102 100644 --- a/src/ZStdCompressor.php +++ b/src/ZStdCompressor.php @@ -13,7 +13,7 @@ final class ZStdCompressor implements CompressorInterface * @link https://github.com/kjdev/php-ext-zstd * @param int $level ZStd Compression Level */ - public function __construct(int $level = ZSTD_COMPRESS_LEVEL_DEFAULT) + public function __construct(int $level = \ZSTD_COMPRESS_LEVEL_DEFAULT) { $this->level = $level; } @@ -25,7 +25,7 @@ public function name(): string public function compress(string $input): string { - $out = zstd_compress($input, $this->level); + $out = \zstd_compress($input, $this->level); if ($out === false) { throw new \RuntimeException('Error occurred while compressing output'); } From bc8c0f70065deb76e2f9317746e4a74d20e97c46 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 13 Jul 2021 22:03:17 +1000 Subject: [PATCH 07/15] added psalm configuration --- psalm.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 psalm.xml diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..3240886 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + From 0695834389510fcef351180ba8d0832797bc91f2 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Wed, 14 Jul 2021 09:23:10 +1000 Subject: [PATCH 08/15] added deflate compressor by default --- src/CompressEncoder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CompressEncoder.php b/src/CompressEncoder.php index 3b9ca5d..4f428e9 100644 --- a/src/CompressEncoder.php +++ b/src/CompressEncoder.php @@ -117,6 +117,7 @@ private function allAvailableCompressors(): array $o[] = new BrotliCompressor(); } $o[] = new GzipCompressor(); + $o[] = new DeflateCompressor(); return $o; } } From 201a95eec03b2af1a5f27512f2a662f9bf540588 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Wed, 14 Jul 2021 10:53:20 +1000 Subject: [PATCH 09/15] added missing strict types & deprecated docs --- src/BrotliCompressor.php | 1 + src/CompressorInterface.php | 1 + src/DeflateCompressor.php | 1 + src/DeflateEncoder.php | 3 +++ src/GzipCompressor.php | 1 + src/GzipEncoder.php | 4 ++++ src/ZStdCompressor.php | 1 + 7 files changed, 12 insertions(+) diff --git a/src/BrotliCompressor.php b/src/BrotliCompressor.php index f9fa895..75344e9 100644 --- a/src/BrotliCompressor.php +++ b/src/BrotliCompressor.php @@ -1,4 +1,5 @@ Date: Wed, 14 Jul 2021 10:54:01 +1000 Subject: [PATCH 10/15] Updated changelog with deprecation for v3 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 890a0e4..a7e3b40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [3.0.0] - 2021-07-14 + +### Added + +- Brotli & ZStd Compression are now support when the correspoding php extensions are avaiable + +### Changed + +- Generic CompressEncoder middleware with with pluggable Compressors now replaces the individual `GzipEncoder` and `DefalteEncoder`. + +### Deprecated + +- The `GzipEncoder` and the `DeflateEncoder` are now deprecated, as they are + just shims for backwards compatibility. Please replace them with `CompressEncoder` + +### Removed + +- The `Encoder` class has been removed, as its no longer functional. + ## [2.1.1] - 2020-12-03 ### Added - Support for PHP 8.0 From e4734fc8fda783f45b15195b47e6274615900e86 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Wed, 14 Jul 2021 11:07:03 +1000 Subject: [PATCH 11/15] updated default zstd compression level --- src/ZStdCompressor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZStdCompressor.php b/src/ZStdCompressor.php index f9045fc..3398be0 100644 --- a/src/ZStdCompressor.php +++ b/src/ZStdCompressor.php @@ -14,7 +14,7 @@ final class ZStdCompressor implements CompressorInterface * @link https://github.com/kjdev/php-ext-zstd * @param int $level ZStd Compression Level */ - public function __construct(int $level = \ZSTD_COMPRESS_LEVEL_DEFAULT) + public function __construct(int $level = 9) { $this->level = $level; } From 2cf47edc0700dcc5e8fac3313cfdaea6b7c0d1b6 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Wed, 14 Jul 2021 11:16:39 +1000 Subject: [PATCH 12/15] update readme --- README.md | 156 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 129 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c15036d..d4c88f5 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,28 @@ ![Testing][ico-ga] [![Total Downloads][ico-downloads]][link-downloads] -Middleware to encode the response body to `gzip` or `deflate` if the `Accept-Encoding` header is present and adds the `Content-Encoding` header. This package is splitted into the following components: +Middleware to encode the response body using any of `gzip`, `deflate`, `brotli` or `zstd` compression (where available) if the `Accept-Encoding` header is present and can be matched. The `Content-Encoding` header is added when the body has been compressed. This package is split into the following components: -* [GzipEncoder](#gzipencoder) -* [DeflateEncoder](#deflateencoder) +* [CompressEncoder](#compressencoder) +* [BrotliCompressor](#brotlicompressor) +* [DeflateCompressor](#deflatecompressor) +* [GzipCompressor](#gzipcompressor) +* [DeflateCompressor](#deflatecompressor) +* (deprecated) [GzipEncoder](#gzipencoder-deprecated) +* (deprecated) [DeflateEncoder](#deflateencoder-deprecated) You can use the component `ContentEncoding` in the [middlewares/negotiation](https://github.com/middlewares/negotiation#contentencoding) to negotiate the encoding to use. ## Requirements * PHP >= 7.2 -* A [PSR-7 http library](https://github.com/middlewares/awesome-psr15-middlewares#psr-7-implementations) -* A [PSR-15 middleware dispatcher](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) +* A [PSR-7 HTTP Library](https://github.com/middlewares/awesome-psr15-middlewares#psr-7-implementations) +* A [PSR-15 Middleware Dispatcher](https://github.com/middlewares/awesome-psr15-middlewares#dispatcher) + +### Optional Requirements + +* [PHP Brotli Extension](https://github.com/kjdev/php-ext-brotli) +* [PHP ZStd Extension](https://github.com/kjdev/php-ext-zstd) ## Installation @@ -26,63 +36,155 @@ This package is installable and autoloadable via Composer as [middlewares/encode composer require middlewares/encoder ``` -## GzipEncoder +## Upgrading from v2.x or earlier -Compress the response body to GZIP format using [gzencode](http://php.net/manual/en/function.gzencode.php) and add the header `Content-Encoding: gzip`. +When upgrading its advised to switch from using the deprecated GzipEncoder and or DeflateEncoder directly, to using the +CompressEncoder middleware. -**Note:** The response body is encoded only if the header contains the value `gzip` in the header `Accept-Encoding`. +```diff +Dispatcher::run([ +... +- new Middlewares\GzipEncoder(), +- new Middlewares\DeflateEncoder(), ++ new Middlewares\CompressEncoder(), +... +]); +``` + +_This configuration will try ZStd, Brotli, GZip, and then Deflate, in that order, if available (php extensions are +loaded for zstd and or brotli)._ + +## CompressEncoder + +Compress the response body to matching `Accept-Encoding` format format using the first matching Compressor (by default +`zstd`, `brotli`, `gzip` and `defalate` are tried, in that order, where php extentions are available) after which the +`Content-Encoding` header will be added to the output. + +**Note:** The response body is encoded only if the `Accept-Encoding` header contains an available compression type, the +Content-Type is considered compressible, and there is no `Content-Encoding` header. ```php Dispatcher::run([ - new Middlewares\GzipEncoder(), + new \Middlewares\CompressEncoder(), ]); ``` -Optionally, you can provide a `Psr\Http\Message\StreamFactoryInterface` that will be used to create the response body. If it's not defined, [Middleware\Utils\Factory](https://github.com/middlewares/utils#factory) will be used to detect it automatically. +#### Optional Parameters: + +- You can provide a `Psr\Http\Message\StreamFactoryInterface` that will be used to create the response body. +If it's not defined, [Middleware\Utils\Factory](https://github.com/middlewares/utils#factory) will be used a default. ```php $streamFactory = new MyOwnStreamFactory(); -$encoder = new Middlewares\GzipEncoder($streamFactory); +$encoder = new Middlewares\CompressEncoder($streamFactory); ``` -## DeflateEncoder +- You can also provide your own list of Compressors (that implement the `CompressorInterface`). For example: -Compress the response body to Deflate format using [gzdeflate](http://php.net/manual/en/function.gzdeflate.php) and add the header `Content-Encoding: deflate`. +```php +$encoder = new \Middlewares\CompressEncoder(null, [ + new MyProject\LzmaEncoder(), + new \Middlewares\GZipEncoder($level = 9), +]); -**Note:** The response body is encoded only if the header contains the value `deflate` in the header `Accept-Encoding`. +``` + +### Only compress specific Content-Types + +This option allows the overriding of the default patterns used to detect what resources are already compressed. + +The default pattern detects the following mime types `text/*`, `application/json`, `image/svg+xml` and empty content +types as compressible. If the pattern begins with a forward slash `/` it is tested as a regular expression, otherwise +its is treated as a case-insensitive string comparison. ```php Dispatcher::run([ - new Middlewares\DeflateEncoder(), + (new \Middlewares\CompressEncoder()) + ->contentType( + '/^application\/pdf$/', // Regular Expression + 'text/csv' // Text Pattern + ) ]); ``` -Optionally, you can provide a `Psr\Http\Message\StreamFactoryInterface` that will be used to create the response body. If it's not defined, [Middleware\Utils\Factory](https://github.com/middlewares/utils#factory) will be used to detect it automatically. +--- + +### BrotliCompressor + +The brotli compressor is used where the `Accept-Encoding` includes `br` and can be configured with a custom compression +level, via a constructor parameter. ```php -$streamFactory = new MyOwnStreamFactory(); +$encoder = new \Middlewares\CompressEncoder(null, [ + new \Middlewares\BrotliCompressor($level = 1), +]); +``` +### DeflateCompressor + +The deflate compressor is used where the `Accept-Encoding` includes `deflate` and can be configured with a custom compression +level, via a constructor parameter. -$encoder = new Middlewares\DeflateEncoder($streamFactory); +```php +$encoder = new \Middlewares\CompressEncoder(null, [ + new \Middlewares\DeflateCompressor($level = 1), +]); ``` -## Common Options +### GzipCompressor -### contentType +The gzip compressor is used where the `Accept-Encoding` includes `gzip` and can be configured with a custom compression +level, via a constructor parameter. -This option allows the overring of the default patterns used to detect what resources are already compressed. +```php +$encoder = new \Middlewares\CompressEncoder(null, [ + new \Middlewares\GzipCompressor($level = 1), +]); +``` + +### ZStdCompressor + +The gzip compressor is used where the `Accept-Encoding` includes `zstd` and can be configured with a custom compression +level, via a constructor parameter. + +```php +$encoder = new \Middlewares\CompressEncoder(null, [ + new \Middlewares\ZStdCompressor($level = 1), +]); +``` + +## GzipEncoder (deprecated) + +**Please note, this is provided for backward compatibility only** + +Compress the response body to GZIP format using [gzencode](http://php.net/manual/en/function.gzencode.php) and add the header `Content-Encoding: gzip`. -The default pattern detects the following mime types `text/*`, `application/json`, `image/svg+xml` and empty content types as compressible. If the pattern begins with a forward slash `/` it is tested as a regular expression, otherwise its is case-insensitive string comparison. +**Note:** The response body is encoded only if the header contains the value `gzip` in the header `Accept-Encoding`. ```php Dispatcher::run([ - (new Middlewares\DeflateEncoder()) - ->contentType( - '/^application\/pdf$/', // Regular Expression - 'text/csv' // Text Pattern - ) + new \Middlewares\GzipEncoder(), +]); +``` + +Optionally, you can provide a `Psr\Http\Message\StreamFactoryInterface` as above. + +## DeflateEncoder (deprecated) + +**Please note, this is provided for backward compatibility only** + +Compress the response body to Deflate format using [gzdeflate](http://php.net/manual/en/function.gzdeflate.php) and add the header `Content-Encoding: deflate`. + +**Note:** The response body is encoded only if the header contains the value `deflate` in the header `Accept-Encoding`. + +```php +Dispatcher::run([ + new \Middlewares\DeflateEncoder(), ]); ``` + +Optionally, you can provide a `Psr\Http\Message\StreamFactoryInterface` as above. + --- Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes and [CONTRIBUTING](CONTRIBUTING.md) for contributing details. From ef1aa307db42fabc82eb7119a52e967d90877fdd Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Wed, 14 Jul 2021 11:20:40 +1000 Subject: [PATCH 13/15] updated namespaces --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d4c88f5..f9c69ad 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ CompressEncoder middleware. ```diff Dispatcher::run([ ... -- new Middlewares\GzipEncoder(), -- new Middlewares\DeflateEncoder(), -+ new Middlewares\CompressEncoder(), +- new \Middlewares\GzipEncoder(), +- new \Middlewares\DeflateEncoder(), ++ new \Middlewares\CompressEncoder(), ... ]); ``` @@ -77,7 +77,7 @@ If it's not defined, [Middleware\Utils\Factory](https://github.com/middlewares/u ```php $streamFactory = new MyOwnStreamFactory(); -$encoder = new Middlewares\CompressEncoder($streamFactory); +$encoder = new \Middlewares\CompressEncoder($streamFactory); ``` - You can also provide your own list of Compressors (that implement the `CompressorInterface`). For example: From 88e3b72553671d82bc18a95d8825ee607c2c5edd Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Wed, 14 Jul 2021 11:22:41 +1000 Subject: [PATCH 14/15] fix namespace and class names --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f9c69ad..95565ac 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ CompressEncoder middleware. ```diff Dispatcher::run([ ... -- new \Middlewares\GzipEncoder(), -- new \Middlewares\DeflateEncoder(), -+ new \Middlewares\CompressEncoder(), +- new Middlewares\GzipEncoder(), +- new Middlewares\DeflateEncoder(), ++ new Middlewares\CompressEncoder(), ... ]); ``` @@ -65,7 +65,7 @@ Content-Type is considered compressible, and there is no `Content-Encoding` head ```php Dispatcher::run([ - new \Middlewares\CompressEncoder(), + new Middlewares\CompressEncoder(), ]); ``` @@ -77,15 +77,15 @@ If it's not defined, [Middleware\Utils\Factory](https://github.com/middlewares/u ```php $streamFactory = new MyOwnStreamFactory(); -$encoder = new \Middlewares\CompressEncoder($streamFactory); +$encoder = new Middlewares\CompressEncoder($streamFactory); ``` -- You can also provide your own list of Compressors (that implement the `CompressorInterface`). For example: +- You can also provide your own list of Compressors (that implement the `Middlewares\CompressorInterface`). For example: ```php -$encoder = new \Middlewares\CompressEncoder(null, [ - new MyProject\LzmaEncoder(), - new \Middlewares\GZipEncoder($level = 9), +$encoder = new Middlewares\CompressEncoder(null, [ + new MyProject\LzmaCompressor(), + new Middlewares\GZipCompressor($level = 9), ]); ``` @@ -100,7 +100,7 @@ its is treated as a case-insensitive string comparison. ```php Dispatcher::run([ - (new \Middlewares\CompressEncoder()) + (new Middlewares\CompressEncoder()) ->contentType( '/^application\/pdf$/', // Regular Expression 'text/csv' // Text Pattern @@ -116,8 +116,8 @@ The brotli compressor is used where the `Accept-Encoding` includes `br` and can level, via a constructor parameter. ```php -$encoder = new \Middlewares\CompressEncoder(null, [ - new \Middlewares\BrotliCompressor($level = 1), +$encoder = new Middlewares\CompressEncoder(null, [ + new Middlewares\BrotliCompressor($level = 1), ]); ``` ### DeflateCompressor @@ -126,8 +126,8 @@ The deflate compressor is used where the `Accept-Encoding` includes `deflate` an level, via a constructor parameter. ```php -$encoder = new \Middlewares\CompressEncoder(null, [ - new \Middlewares\DeflateCompressor($level = 1), +$encoder = new Middlewares\CompressEncoder(null, [ + new Middlewares\DeflateCompressor($level = 1), ]); ``` @@ -137,8 +137,8 @@ The gzip compressor is used where the `Accept-Encoding` includes `gzip` and can level, via a constructor parameter. ```php -$encoder = new \Middlewares\CompressEncoder(null, [ - new \Middlewares\GzipCompressor($level = 1), +$encoder = new Middlewares\CompressEncoder(null, [ + new Middlewares\GzipCompressor($level = 1), ]); ``` @@ -148,8 +148,8 @@ The gzip compressor is used where the `Accept-Encoding` includes `zstd` and can level, via a constructor parameter. ```php -$encoder = new \Middlewares\CompressEncoder(null, [ - new \Middlewares\ZStdCompressor($level = 1), +$encoder = new Middlewares\CompressEncoder(null, [ + new Middlewares\ZStdCompressor($level = 1), ]); ``` @@ -163,7 +163,7 @@ Compress the response body to GZIP format using [gzencode](http://php.net/manual ```php Dispatcher::run([ - new \Middlewares\GzipEncoder(), + new Middlewares\GzipEncoder(), ]); ``` @@ -179,7 +179,7 @@ Compress the response body to Deflate format using [gzdeflate](http://php.net/ma ```php Dispatcher::run([ - new \Middlewares\DeflateEncoder(), + new Middlewares\DeflateEncoder(), ]); ``` From 75f14dcf8ad63948b06a457270f0c064c3e40ba6 Mon Sep 17 00:00:00 2001 From: Chris Seufert Date: Tue, 9 Jan 2024 14:43:48 +1100 Subject: [PATCH 15/15] Update to v4 of middleware/utils --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8d0f3e6..a3d45ee 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require": { "ext-zlib": "*", "php": "^7.2 || ^8.0", - "middlewares/utils": "^3.0", + "middlewares/utils": "^4.0", "psr/http-server-middleware": "^1.0" }, "require-dev": {