Skip to content

Commit

Permalink
Add Guzzle Timeout To Prerender Middleware (#39)
Browse files Browse the repository at this point in the history
Also adds new `prerender.timeout` config option. Defaults to no timeout

Co-authored-by: Keller Martin <[email protected]>
  • Loading branch information
kellerjmrtn and Keller Martin authored Jun 21, 2024
1 parent c31fb16 commit 44809f1
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 21 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,21 @@ return [
'Qwantify'
],

/*
|--------------------------------------------------------------------------
| Timeout
|--------------------------------------------------------------------------
|
| Specifies the Guzzle request timeout in seconds. If the request for a
| prerendered page takes longer than this, the request will be terminated
| and the page will be loaded without prerender. A value of 0 means no
| timeout.
|
| See: https://docs.guzzlephp.org/en/stable/request-options.html#timeout
|
*/
'timeout' => env('PRERENDER_TIMEOUT', 0),

];
```

Expand Down
15 changes: 15 additions & 0 deletions config/prerender.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,19 @@
'Qwantify',
],

/*
|--------------------------------------------------------------------------
| Timeout
|--------------------------------------------------------------------------
|
| Specifies the Guzzle request timeout in seconds. If the request for a
| prerendered page takes longer than this, the request will be terminated
| and the page will be loaded without prerender. A value of 0 means no
| timeout.
|
| See: https://docs.guzzlephp.org/en/stable/request-options.html#timeout
|
*/
'timeout' => env('PRERENDER_TIMEOUT', 0),

];
45 changes: 24 additions & 21 deletions src/PrerenderMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Closure;
use GuzzleHttp\Client as Guzzle;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -71,15 +72,15 @@ public function __construct(Guzzle $client)
{
$this->returnSoftHttpCodes = config('prerender.prerender_soft_http_codes');

if ($this->returnSoftHttpCodes) {
$this->client = $client;
} else {
// Workaround to avoid following redirects
$config = $client->getConfig();
$config['allow_redirects'] = false;
$this->client = new Guzzle($config);
$guzzleConfig = $client->getConfig();
$guzzleConfig['timeout'] = config('prerender.timeout');

if (!$this->returnSoftHttpCodes) {
$guzzleConfig['allow_redirects'] = false;
}

$this->client = new Guzzle($guzzleConfig);

$config = config('prerender');

$this->prerenderUri = $config['prerender_url'];
Expand All @@ -102,7 +103,7 @@ public function handle(Request $request, Closure $next)
if ($prerenderedResponse) {
$statusCode = $prerenderedResponse->getStatusCode();

if (! $this->returnSoftHttpCodes && $statusCode >= 300 && $statusCode < 400) {
if (!$this->returnSoftHttpCodes && $statusCode >= 300 && $statusCode < 400) {
$headers = $prerenderedResponse->getHeaders();

return Redirect::to(array_change_key_case($headers, CASE_LOWER)['location'][0], $statusCode);
Expand All @@ -128,11 +129,11 @@ private function shouldShowPrerenderedPage(Request $request): bool

$isRequestingPrerenderedPage = false;

if (! $userAgent) {
if (!$userAgent) {
return false;
}

if (! $request->isMethod('GET')) {
if (!$request->isMethod('GET')) {
return false;
}

Expand All @@ -152,13 +153,13 @@ private function shouldShowPrerenderedPage(Request $request): bool
$isRequestingPrerenderedPage = true;
}

if (! $isRequestingPrerenderedPage) {
if (!$isRequestingPrerenderedPage) {
return false;
}

// only check whitelist if it is not empty
if ($this->whitelist) {
if (! $this->isListed($requestUri, $this->whitelist)) {
if (!$this->isListed($requestUri, $this->whitelist)) {
return false;
}
}
Expand Down Expand Up @@ -207,19 +208,21 @@ private function getPrerenderedPageResponse(Request $request): ?ResponseInterfac

return $this->client->get($this->prerenderUri.'/'.urlencode($protocol.'://'.$host.'/'.$path), compact('headers'));
} catch (RequestException $exception) {
if (! $this->returnSoftHttpCodes && ! empty($exception->getResponse()) && $exception->getResponse()->getStatusCode() === 404) {
if (!$this->returnSoftHttpCodes && !empty($exception->getResponse()) && $exception->getResponse()->getStatusCode() === 404) {
abort(404);
}
} catch (ConnectException $exception) {
//
}

// In case of an exception, we only throw the exception if we are in debug mode. Otherwise,
// we return null and the handle() method will just pass the request to the next middleware
// and we do not show a prerendered page.
if (config('app.debug')) {
throw $exception;
}

return null;
// In case of an exception, we only throw the exception if we are in debug mode. Otherwise,
// we return null and the handle() method will just pass the request to the next middleware
// and we do not show a prerendered page.
if (config('app.debug')) {
throw $exception;
}

return null;
}

/**
Expand Down
16 changes: 16 additions & 0 deletions tests/PrerenderMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace CodebarAg\LaravelPrerender\Tests;

use GuzzleHttp\Client;

class PrerenderMiddlewareTest extends TestCase
{
/** @test */
Expand Down Expand Up @@ -78,6 +80,20 @@ public function it_should_not_prerender_page_when_missing_user_agent()
->assertSee('GET - Success');
}

/** @test */
public function it_should_not_prerender_page_if_request_times_out()
{
$this->app->bind(Client::class, function () {
return $this->createMockTimeoutClient();
});

$this->allowSymfonyUserAgent();

$this->get('/test-middleware')
->assertHeaderMissing('prerender.io-mock')
->assertSee('GET - Success');
}

private function allowSymfonyUserAgent()
{
config()->set('prerender.crawler_user_agents', ['symfony']);
Expand Down
13 changes: 13 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
use CodebarAg\LaravelPrerender\LaravelPrerenderServiceProvider;
use CodebarAg\LaravelPrerender\PrerenderMiddleware;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Http\Kernel;
Expand Down Expand Up @@ -49,6 +51,17 @@ protected function getEnvironmentSetUp($app): void
});
}

protected function createMockTimeoutClient(): Client
{
$mock = new MockHandler([
new ConnectException('Could not connect', new Request('GET', 'test')),
]);

$stack = HandlerStack::create($mock);

return new Client(['handler' => $stack]);
}

protected function setupRoutes(): void
{
Route::get('test-middleware', function () {
Expand Down

0 comments on commit 44809f1

Please sign in to comment.