-
-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Feature Request
| Q | A |
|---|---|
| New Feature | yes |
| RFC | yes |
| BC Break | no |
Summary
I recently found out, that request payloads usually won't be compressed, as the client does not "challenge" possible encodings with the server.
After enforcing Content-Encoding: gzip on the client side (because we are requesting an internal API), I also found out that the Webserver (NGINX in our case) is not able to decompress incoming requests so that the application only gets the decompressed content. There is no such configuration without having annoying lua scripts within NGINX and thus, I've created a middleware to handle this.
use Api\Exception\InvalidRequestPayloadException;
use Fig\Http\Message\StatusCodeInterface;
use Mezzio\ProblemDetails\ProblemDetailsResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RuntimeException;
use Safe\Exceptions\ZlibException;
use function sprintf;
final class ContentEncodingDecoderMiddleware implements MiddlewareInterface
{
private const CONTENT_ENCODING_GZIP = 'gzip';
/**
* @var StreamFactoryInterface
*/
private $streamFactory;
/**
* @var ProblemDetailsResponseFactory
*/
private $responseFactory;
public function __construct(StreamFactoryInterface $streamFactory, ProblemDetailsResponseFactory $responseFactory)
{
$this->streamFactory = $streamFactory;
$this->responseFactory = $responseFactory;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$request->hasHeader('Content-Encoding')) {
return $handler->handle($request);
}
$encoding = mb_strtolower($request->getHeaderLine('Content-Encoding'));
if ($encoding !== self::CONTENT_ENCODING_GZIP) {
return $this->responseFactory->createResponse(
$request,
StatusCodeInterface::STATUS_NOT_ACCEPTABLE,
sprintf(
'Request contains content with an unsupported encoding "%s". Only "%s" is supported!',
$encoding,
self::CONTENT_ENCODING_GZIP
),
'Unacceptable Content Encoding',
'https://docs.handyvertrag.check24.de/problem/unacceptable-content-encoding/'
);
}
if ($request->getParsedBody() !== null) {
throw new RuntimeException(
'The request already contains a parsed body.'
. ' Please ensure that the `BodyParamsMiddleware` comes after this middleware!'
);
}
return $handler->handle($request->withBody($this->decode($request->getBody())));
}
private function decode(StreamInterface $payload): StreamInterface
{
$contents = (string) $payload;
try {
$decoded = \Safe\gzuncompress($contents);
} catch (ZlibException $exception) {
throw InvalidRequestPayloadException::create($exception);
}
return $this->streamFactory->createStream($decoded);
}
}This middleware could be modified to handle multiple Content-Encoding header values (gzip, br, e.g.).
Thus, I think we might want to have some kind of ContentDecompressionInterface like:
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
interface ContentDecompressionInterface
{
public function canDecompress(ServerRequestInterface $request): bool;
public function decompress(StreamInterface $body): StreamInterface;
}Having gzip integrated with the middleware per-default would be okay for me.
If anyone needs additional support, the interface can be used to implement all other types of encodings.
I am open to remove the dependency of the BodyParamsMiddleware as this would limit the requests to JSON/Form requests.
Validation of the request methods with an exclude list might be a good idea aswell. Thats what BodyParamsMiddleware does aswell.
Having these methods available as a constant would be something I'd like to see aswell. Maybe thats something we could contribute to fig/http-message-util @weierophinney?