Skip to content

Commit

Permalink
Track redirect HTTP status codes (#1711)
Browse files Browse the repository at this point in the history
  • Loading branch information
mallardduck authored and sagikazarmark committed Jan 15, 2017
1 parent 3a66f89 commit 77a16c8
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 9 deletions.
45 changes: 45 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,48 @@ setting the ``expect`` request option to ``false``:
// Disable the expect header on all client requests
$client = new GuzzleHttp\Client(['expect' => false]);
How can I track a redirected requests?
======================================

You can enable tracking of redirected URIs and status codes via the
`track_redirects` option. Each redirected URI and status code will be stored in the
``X-Guzzle-Redirect-History`` and the ``X-Guzzle-Redirect-Status-History``
header respectively.

The initial request's URI and the final status code will be excluded from the results.
With this in mind you should be able to easily track a request's full redirect path.

For example, let's say you need to track redirects and provide both results
together in a single report:

.. code-block:: php
// First you configure Guzzle with redirect tracking and make a request
$client = new Client([
RequestOptions::ALLOW_REDIRECTS => [
'max' => 10, // allow at most 10 redirects.
'strict' => true, // use "strict" RFC compliant redirects.
'referer' => true, // add a Referer header
'track_redirects' => true,
],
]);
$initialRequest = '/redirect/3'; // Store the request URI for later use
$response = $client->request('GET', $initialRequest); // Make your request
// Retrieve both Redirect History headers
$redirectUriHistory = $response->getHeader('X-Guzzle-Redirect-History'); // retrieve Redirect URI history
$redirectCodeHistory = $response->getHeader('X-Guzzle-Redirect-Status-History'); // retrieve Redirect HTTP Status history
// Add the initial URI requested to the (beginning of) URI history
array_unshift($redirectUriHistory, $initialRequest);
// Add the final HTTP status code to the end of HTTP response history
array_push($redirectCodeHistory, $response->getStatusCode());
// (Optional) Combine the items of each array into a single result set
$fullRedirectReport = [];
foreach ($redirectUriHistory as $key => $value) {
$fullRedirectReport[$key] = ['location' => $value, 'code' => $redirectCodeHistory[$key]];
}
echo json_encode($fullRedirectReport);
14 changes: 11 additions & 3 deletions docs/request-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,14 @@ pairs:
is encountered. The callable is invoked with the original request and the
redirect response that was received. Any return value from the on_redirect
function is ignored.
- track_redirects: (bool) When set to ``true``, each redirected URI encountered
will be tracked in the ``X-Guzzle-Redirect-History`` header in the order in
which the redirects were encountered.
- track_redirects: (bool) When set to ``true``, each redirected URI and status
code encountered will be tracked in the ``X-Guzzle-Redirect-History`` and
``X-Guzzle-Redirect-Status-History`` headers respectively. All URIs and
status codes will be stored in the order which the redirects were encountered.

Note: When tracking redirects the ``X-Guzzle-Redirect-History`` header will
exclude the initial request's URI and the ``X-Guzzle-Redirect-Status-History``
header will exclude the final status code.

.. code-block:: php
Expand Down Expand Up @@ -105,6 +110,9 @@ pairs:
echo $res->getHeaderLine('X-Guzzle-Redirect-History');
// http://first-redirect, http://second-redirect, etc...
echo $res->getHeaderLine('X-Guzzle-Redirect-Status-History');
// 301, 302, etc...
.. warning::

This option only has an effect if your handler has the
Expand Down
18 changes: 12 additions & 6 deletions src/RedirectMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class RedirectMiddleware
{
const HISTORY_HEADER = 'X-Guzzle-Redirect-History';

const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';

public static $defaultSettings = [
'max' => 5,
'protocols' => ['http', 'https'],
Expand Down Expand Up @@ -108,23 +110,27 @@ public function checkRedirect(
if (!empty($options['allow_redirects']['track_redirects'])) {
return $this->withTracking(
$promise,
(string) $nextRequest->getUri()
(string) $nextRequest->getUri(),
$response->getStatusCode()
);
}

return $promise;
}

private function withTracking(PromiseInterface $promise, $uri)
private function withTracking(PromiseInterface $promise, $uri, $statusCode)
{
return $promise->then(
function (ResponseInterface $response) use ($uri) {
function (ResponseInterface $response) use ($uri, $statusCode) {
// Note that we are pushing to the front of the list as this
// would be an earlier response than what is currently present
// in the history header.
$header = $response->getHeader(self::HISTORY_HEADER);
array_unshift($header, $uri);
return $response->withHeader(self::HISTORY_HEADER, $header);
$historyHeader = $response->getHeader(self::HISTORY_HEADER);
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
array_unshift($historyHeader, $uri);
array_unshift($statusHeader, $statusCode);
return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
}
);
}
Expand Down
29 changes: 29 additions & 0 deletions tests/RedirectMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,35 @@ public function testAddsGuzzleRedirectHeader()
);
}

public function testAddsGuzzleRedirectStatusHeader()
{
$mock = new MockHandler([
new Response(301, ['Location' => 'http://example.com']),
new Response(302, ['Location' => 'http://example.com/foo']),
new Response(301, ['Location' => 'http://example.com/bar']),
new Response(302, ['Location' => 'http://example.com/baz']),
new Response(200)
]);

$stack = new HandlerStack($mock);
$stack->push(Middleware::redirect());
$handler = $stack->resolve();
$request = new Request('GET', 'http://example.com?a=b');
$promise = $handler($request, [
'allow_redirects' => ['track_redirects' => true]
]);
$response = $promise->wait(true);
$this->assertEquals(
[
301,
302,
301,
302,
],
$response->getHeader(RedirectMiddleware::STATUS_HISTORY_HEADER)
);
}

public function testDoesNotAddRefererWhenGoingFromHttpsToHttp()
{
$mock = new MockHandler([
Expand Down

0 comments on commit 77a16c8

Please sign in to comment.