Skip to content

Commit

Permalink
#1: added follow redirect middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
proggeler committed Oct 24, 2022
1 parent fac96b3 commit 7f82a66
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 0 deletions.
64 changes: 64 additions & 0 deletions src/Middleware/FollowRedirectMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace DMT\Http\Client\Middleware;

use DMT\Http\Client\MiddlewareInterface;
use DMT\Http\Client\RequestHandlerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriFactoryInterface;

class FollowRedirectMiddleware implements MiddlewareInterface
{
protected array $statusCodes = [
301, // Moved Permanently
302, // Found
303, // See Other
307, // Temporary Redirect
308, // Permanent Redirect
];

private UriFactoryInterface $factory;

/**
* @param UriFactoryInterface $factory
* @param array|null $statusCodes
*/
public function __construct(UriFactoryInterface $factory, array $statusCodes = null)
{
$this->factory = $factory;
$this->statusCodes = $statusCodes ?? $this->statusCodes;
}

/**
* Automatic follow redirects.
*
* @param RequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws ClientExceptionInterface
*/
public function process(RequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);

if (!in_array($response->getStatusCode(), $this->statusCodes)) {
return $response;
}

if (in_array($response->getStatusCode(), [301, 302, 303])
&& !in_array(strtoupper($request->getMethod()), ['GET', 'HEAD', 'OPTIONS'])
) {
$request = $request->withMethod('GET');
}

$location = $response->getHeaderLine('location');
if ($location) {
$response = $handler->handle(
$request->withUri($this->factory->createUri($location))
);
}

return $response;
}
}
93 changes: 93 additions & 0 deletions tests/Middleware/FollowRedirectMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace DMT\Test\Http\Client\Middleware;

use DMT\Http\Client\Middleware\FollowRedirectMiddleware;
use DMT\Http\Client\RequestHandler;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;

class FollowRedirectMiddlewareTest extends TestCase
{
/**
* @dataProvider provideFollowRedirect
*
* @param string $method
* @param int $statusCode
* @param string $expectedMethod
*/
public function testProcess(string $method, int $statusCode, string $expectedMethod)
{
$client = $this->getMockBuilder(Client::class)
->onlyMethods(['sendRequest'])
->getMockForAbstractClass();

$client
->expects($this->exactly(2))
->method('sendRequest')
->will(
$this->onConsecutiveCalls(
new Response($statusCode, ['Location' => 'https://new-location.org/path']),
$this->returnCallback(
function (RequestInterface $request) {
return new Response(200, ['RequestMethod' => $request->getMethod()]);
}
)
)
);

$requestHandler = new RequestHandler($client, new FollowRedirectMiddleware(new HttpFactory()));
$response = $requestHandler->handle(new Request($method, 'https://some-location.org/path'));

$this->assertSame(200, $response->getStatusCode());
$this->assertSame($expectedMethod, $response->getHeaderLine('requestMethod'));
}

public function provideFollowRedirect(): iterable
{
return [
['POST', 301, 'GET'],
['GET', 302, 'GET'],
['HEAD', 302, 'HEAD'],
['PUT', 303, 'GET'],
['PUT', 307, 'PUT'],
['POST', 308, 'POST'],
];
}

/**
* @dataProvider provideNoFollow
*
* @param string $method
* @param int $statusCode
* @param array $headers
*/
public function testProcessNoFollow(string $method, int $statusCode, array $headers = [])
{
$client = $this->getMockBuilder(Client::class)
->onlyMethods(['sendRequest'])
->getMockForAbstractClass();

$client
->expects($this->exactly(1))
->method('sendRequest')
->willReturn($response = new Response($statusCode, $headers));

$requestHandler = new RequestHandler($client, new FollowRedirectMiddleware(new HttpFactory()));

$this->assertSame($response, $requestHandler->handle(new Request($method, 'https://some-location.org/path')));
}

public function provideNoFollow(): iterable
{
return [
['PUT', 300, ['Location' => 'https://new-location.org/path']],
['POST', 303],
['GET', 304],
];
}
}

0 comments on commit 7f82a66

Please sign in to comment.