Add ETag to FeedController
This commit is contained in:
parent
dc216a0464
commit
72d48de3ae
|
@ -48,6 +48,7 @@ return [
|
|||
|
||||
// Changes of request/response parameters
|
||||
\Engelsystem\Middleware\SetLocale::class,
|
||||
\Engelsystem\Middleware\ETagHandler::class,
|
||||
\Engelsystem\Middleware\AddHeaders::class,
|
||||
|
||||
// The application code
|
||||
|
|
|
@ -33,7 +33,7 @@ class FeedController extends BaseController
|
|||
{
|
||||
$news = $this->getNews();
|
||||
|
||||
return $this->response
|
||||
return $this->withEtag($news)
|
||||
->withHeader('content-type', 'application/atom+xml; charset=utf-8')
|
||||
->withView('api/atom', ['news' => $news]);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class FeedController extends BaseController
|
|||
{
|
||||
$news = $this->getNews();
|
||||
|
||||
return $this->response
|
||||
return $this->withEtag($news)
|
||||
->withHeader('content-type', 'application/rss+xml; charset=utf-8')
|
||||
->withView('api/rss', ['news' => $news]);
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class FeedController extends BaseController
|
|||
{
|
||||
$shifts = $this->getShifts();
|
||||
|
||||
return $this->response
|
||||
return $this->withEtag($shifts)
|
||||
->withHeader('content-type', 'text/calendar; charset=utf-8')
|
||||
->withHeader('content-disposition', 'attachment; filename=shifts.ics')
|
||||
->withView('api/ical', ['shiftEntries' => $shifts]);
|
||||
|
@ -121,11 +121,18 @@ class FeedController extends BaseController
|
|||
];
|
||||
}
|
||||
|
||||
return $this->response
|
||||
return $this->withEtag($response)
|
||||
->withAddedHeader('content-type', 'application/json; charset=utf-8')
|
||||
->withContent(json_encode($response));
|
||||
}
|
||||
|
||||
protected function withEtag(mixed $value): Response
|
||||
{
|
||||
$hash = md5(json_encode($value));
|
||||
|
||||
return $this->response->setEtag($hash);
|
||||
}
|
||||
|
||||
protected function getNews(): Collection
|
||||
{
|
||||
$news = $this->request->has('meetings')
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Middleware;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Nyholm\Psr7\Stream;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ETagHandler implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Compare the response ETag to a requested If-None-Match header and send a 304 "not modified" if they match
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
|
||||
$etagMatch = $request->getHeader('If-None-Match');
|
||||
$etag = $response->getHeader('ETag');
|
||||
|
||||
if (
|
||||
!$etagMatch
|
||||
|| !$etag
|
||||
|| !Str::contains(implode(', ', $etagMatch), trim($etag[0], '"'))
|
||||
) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $response
|
||||
->withStatus(Response::HTTP_NOT_MODIFIED)
|
||||
->withBody(Stream::create());
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ class FeedControllerTest extends ControllerTest
|
|||
/**
|
||||
* @covers \Engelsystem\Controllers\FeedController::__construct
|
||||
* @covers \Engelsystem\Controllers\FeedController::atom
|
||||
* @covers \Engelsystem\Controllers\FeedController::withEtag
|
||||
*/
|
||||
public function testAtom(): void
|
||||
{
|
||||
|
@ -33,6 +34,12 @@ class FeedControllerTest extends ControllerTest
|
|||
['content-type', 'application/atom+xml; charset=utf-8'],
|
||||
$this->response
|
||||
);
|
||||
$this->response->expects($this->once())
|
||||
->method('setEtag')
|
||||
->willReturnCallback(function ($etag) {
|
||||
$this->assertNotEmpty($etag);
|
||||
return $this->response;
|
||||
});
|
||||
$this->response->expects($this->once())
|
||||
->method('withView')
|
||||
->willReturnCallback(function ($view, $data) {
|
||||
|
@ -58,6 +65,7 @@ class FeedControllerTest extends ControllerTest
|
|||
['content-type', 'application/rss+xml; charset=utf-8'],
|
||||
$this->response
|
||||
);
|
||||
$this->setExpects($this->response, 'setEtag', null, $this->response);
|
||||
$this->response->expects($this->once())
|
||||
->method('withView')
|
||||
->willReturnCallback(function ($view, $data) {
|
||||
|
@ -66,6 +74,7 @@ class FeedControllerTest extends ControllerTest
|
|||
|
||||
return $this->response;
|
||||
});
|
||||
|
||||
$controller->rss();
|
||||
}
|
||||
|
||||
|
@ -95,6 +104,8 @@ class FeedControllerTest extends ControllerTest
|
|||
)
|
||||
->willReturn($this->response);
|
||||
|
||||
$this->setExpects($this->response, 'setEtag', null, $this->response);
|
||||
|
||||
$this->response->expects($this->once())
|
||||
->method('withView')
|
||||
->willReturnCallback(function ($view, $data) {
|
||||
|
@ -109,6 +120,7 @@ class FeedControllerTest extends ControllerTest
|
|||
|
||||
return $this->response;
|
||||
});
|
||||
|
||||
$controller->ical();
|
||||
}
|
||||
|
||||
|
@ -137,6 +149,8 @@ class FeedControllerTest extends ControllerTest
|
|||
$this->response
|
||||
);
|
||||
|
||||
$this->setExpects($this->response, 'setEtag', null, $this->response);
|
||||
|
||||
$this->response->expects($this->once())
|
||||
->method('withContent')
|
||||
->willReturnCallback(function ($jsonData) {
|
||||
|
@ -162,6 +176,7 @@ class FeedControllerTest extends ControllerTest
|
|||
|
||||
return $this->response;
|
||||
});
|
||||
|
||||
$controller->shifts();
|
||||
}
|
||||
|
||||
|
@ -184,6 +199,7 @@ class FeedControllerTest extends ControllerTest
|
|||
|
||||
$this->request->attributes->set('meetings', $isMeeting);
|
||||
$this->setExpects($this->response, 'withHeader', null, $this->response);
|
||||
$this->setExpects($this->response, 'setEtag', null, $this->response);
|
||||
$this->response->expects($this->once())
|
||||
->method('withView')
|
||||
->willReturnCallback(function ($view, $data) use ($isMeeting) {
|
||||
|
@ -210,6 +226,7 @@ class FeedControllerTest extends ControllerTest
|
|||
$controller = new FeedController($this->auth, $this->request, $this->response);
|
||||
|
||||
$this->setExpects($this->response, 'withHeader', null, $this->response);
|
||||
$this->setExpects($this->response, 'setEtag', null, $this->response);
|
||||
$this->response->expects($this->once())
|
||||
->method('withView')
|
||||
->willReturnCallback(function ($view, $data) {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Engelsystem\Test\Unit\Middleware;
|
||||
|
||||
use Engelsystem\Http\Request;
|
||||
use Engelsystem\Http\Response;
|
||||
use Engelsystem\Middleware\ETagHandler;
|
||||
use Engelsystem\Test\Unit\TestCase;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ETagHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Engelsystem\Middleware\ETagHandler::process
|
||||
*/
|
||||
public function testRegister(): void
|
||||
{
|
||||
/** @var RequestHandlerInterface|MockObject $handler */
|
||||
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
|
||||
$request = Request::create('https://localhost')
|
||||
->withHeader('If-None-Match', 'FooBarBaz');
|
||||
$originalResponse = (new Response())
|
||||
->withHeader('ETag', '"FooBarBaz"')
|
||||
->withHeader('original-header', 'value')
|
||||
->withContent('Foo bar!');
|
||||
$this->setExpects($handler, 'handle', [$request], $originalResponse);
|
||||
|
||||
$middleware = new ETagHandler();
|
||||
$response = $middleware->process($request, $handler);
|
||||
|
||||
$this->assertTrue($response->hasHeader('original-header'));
|
||||
$this->assertEquals('value', $response->getHeader('original-header')[0]);
|
||||
|
||||
$this->assertEquals(304, $response->getStatusCode());
|
||||
$this->assertEquals('', (string) $response->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Middleware\ETagHandler::process
|
||||
*/
|
||||
public function testRegisterNoChange(): void
|
||||
{
|
||||
/** @var RequestHandlerInterface|MockObject $handler */
|
||||
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
|
||||
$request = Request::create('https://localhost');
|
||||
$originalResponse = new Response();
|
||||
$this->setExpects($handler, 'handle', [$request], $originalResponse);
|
||||
|
||||
$middleware = new ETagHandler();
|
||||
$response = $middleware->process($request, $handler);
|
||||
|
||||
$this->assertEquals($originalResponse, $response);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue