Skip to content

Commit

Permalink
Merge pull request #67 from acelaya-forks/feature/content-length
Browse files Browse the repository at this point in the history
Feature/content length
  • Loading branch information
acelaya authored Feb 12, 2021
2 parents 62d4b84 + 2c7430a commit 2cf5e45
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* [#60](https://github.com/shlinkio/shlink-common/issues/60) Added support for `pagerfanta/core` as a pagination system.
* [#64](https://github.com/shlinkio/shlink-common/issues/64) Added new input factory methods.
* Added named constructors to `DateRange`.
* Created new `ContentLengthMiddleware`.

### Changed
* *Nothing*
Expand Down
2 changes: 2 additions & 0 deletions config/dependencies.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
Filesystem::class => InvokableFactory::class,

Middleware\CloseDbConnectionMiddleware::class => ConfigAbstractFactory::class,
Middleware\ContentLengthMiddleware::class => InvokableFactory::class,
IpAddress::class => Middleware\IpAddressMiddlewareFactory::class,

Logger\ErrorLogger::class => ConfigAbstractFactory::class,
],
],
Expand Down
40 changes: 40 additions & 0 deletions src/Middleware/ContentLengthMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Common\Middleware;

use Closure;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

use function extension_loaded;

class ContentLengthMiddleware implements MiddlewareInterface
{
private Closure $isSwoole;

public function __construct(?callable $isSwoole = null)
{
$this->isSwoole = $isSwoole !== null
? Closure::fromCallable($isSwoole)
: static fn () => extension_loaded('swoole');
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
if (($this->isSwoole)() || $response->hasHeader('Content-Length')) {
return $response;
}

$bodySize = $response->getBody()->getSize();
if ($bodySize === null) {
return $response;
}

return $response->withHeader('Content-Length', (string) $bodySize);
}
}
110 changes: 110 additions & 0 deletions test/Middleware/ContentLengthMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace ShlinkioTest\Shlink\Common\Middleware;

use Laminas\Diactoros\ServerRequestFactory;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Common\Middleware\ContentLengthMiddleware;

class ContentLengthMiddlewareTest extends TestCase
{
use ProphecyTrait;

private ContentLengthMiddleware $middleware;
private bool $isSwoole;
private ObjectProphecy $handler;

public function setUp(): void
{
$this->isSwoole = false;
$this->middleware = new ContentLengthMiddleware(fn () => $this->isSwoole);
$this->handler = $this->prophesize(RequestHandlerInterface::class);
}

/** @test */
public function responseIsReturnedAsIsWhenIsSwoole(): void
{
$this->isSwoole = true;
$resp = $this->prophesize(ResponseInterface::class);
$respMock = $resp->reveal();
$request = ServerRequestFactory::fromGlobals();
$handle = $this->handler->handle($request)->willReturn($respMock);

$result = $this->middleware->process($request, $this->handler->reveal());

self::assertSame($respMock, $result);
$handle->shouldHaveBeenCalledOnce();
$resp->hasHeader('Content-Length')->shouldNotHaveBeenCalled();
$resp->getBody()->shouldNotHaveBeenCalled();
}

/** @test */
public function responseIsReturnedAsIsWhenItAlreadyHasContentLength(): void
{
$resp = $this->prophesize(ResponseInterface::class);
$hasHeader = $resp->hasHeader('Content-Length')->willReturn(true);
$respMock = $resp->reveal();
$request = ServerRequestFactory::fromGlobals();
$handle = $this->handler->handle($request)->willReturn($respMock);

$result = $this->middleware->process($request, $this->handler->reveal());

self::assertSame($respMock, $result);
$handle->shouldHaveBeenCalledOnce();
$hasHeader->shouldHaveBeenCalledOnce();
$resp->getBody()->shouldNotHaveBeenCalled();
}

/** @test */
public function responseIsReturnedAsIsWhenBodySizeIsNull(): void
{
$resp = $this->prophesize(ResponseInterface::class);
$hasHeader = $resp->hasHeader('Content-Length')->willReturn(false);
$body = $this->prophesize(StreamInterface::class);
$getSize = $body->getSize()->willReturn(null);
$getBody = $resp->getBody()->willReturn($body->reveal());
$respMock = $resp->reveal();
$request = ServerRequestFactory::fromGlobals();
$handle = $this->handler->handle($request)->willReturn($respMock);

$result = $this->middleware->process($request, $this->handler->reveal());

self::assertSame($respMock, $result);
$handle->shouldHaveBeenCalledOnce();
$hasHeader->shouldHaveBeenCalledOnce();
$getSize->shouldHaveBeenCalledOnce();
$getBody->shouldHaveBeenCalledOnce();
$resp->withHeader(Argument::cetera())->shouldNotHaveBeenCalled();
}

/** @test */
public function responseIsReturnedWithNewHeaderWhenBodySizeIsNotNull(): void
{
$resp = $this->prophesize(ResponseInterface::class);
$hasHeader = $resp->hasHeader('Content-Length')->willReturn(false);
$body = $this->prophesize(StreamInterface::class);
$getSize = $body->getSize()->willReturn(100);
$getBody = $resp->getBody()->willReturn($body->reveal());
$respMock = $resp->reveal();
$withHeader = $resp->withHeader('Content-Length', '100')->willReturn($respMock);
$request = ServerRequestFactory::fromGlobals();
$handle = $this->handler->handle($request)->willReturn($respMock);

$result = $this->middleware->process($request, $this->handler->reveal());

self::assertSame($respMock, $result);
$handle->shouldHaveBeenCalledOnce();
$hasHeader->shouldHaveBeenCalledOnce();
$getSize->shouldHaveBeenCalledOnce();
$getBody->shouldHaveBeenCalledOnce();
$withHeader->shouldHaveBeenCalledOnce();
}
}

0 comments on commit 2cf5e45

Please sign in to comment.