Skip to content

Profiling might not work with CachePlugin #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 28, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions Collector/ProfilePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,60 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
return $first($request);
};

return $this->plugin->handleRequest($request, $wrappedNext, $wrappedFirst)->then(function (ResponseInterface $response) use ($profile) {
try {
$promise = $this->plugin->handleRequest($request, $wrappedNext, $wrappedFirst);
} catch (Exception $e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where is this exception thrown?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's thrown from any plugin or even the client.

$profile->setFailed(true);
$profile->setResponse($this->formatter->formatException($e));
$this->collectRequestInformation($request, $stack);

throw $e;
}

return $promise->then(function (ResponseInterface $response) use ($profile, $request, $stack) {
$profile->setResponse($this->formatter->formatResponse($response));
$this->collectRequestInformation($request, $stack);

return $response;
}, function (Exception $exception) use ($profile) {
}, function (Exception $exception) use ($profile, $request, $stack) {
$profile->setFailed(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where is this exception thrown?

How does it differ from https://github.com/php-http/HttplugBundle/pull/175/files#r124504876

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception can be thrown from anywhere too.

But, as some Http\Promise\Promise implementation does not catch all exceptions, we have to use both to catch exceptions in any case. See Http\Promise\FulfilledPromise and Http\Client\Promise\HttpfulfilledPromise for implementation differences.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey. I see. Thanks

$profile->setResponse($this->formatter->formatException($exception));
$this->collectRequestInformation($request, $stack);

throw $exception;
});
}

/**
* Collect request information when not already done by the HTTP client. This happens when using the CachePlugin
* and the cache is hit without re-validation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with "without re-validation"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean when the plugin does not add If-Modified-Since or If-None-Match header.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

*
* @param RequestInterface $request
* @param Stack|null $stack
*/
private function collectRequestInformation(RequestInterface $request, Stack $stack = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So running this does only have an impact when cache plugin and "without re-validation"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the request goes through the whole chain, request information will be already collected by the ProfileClient.

{
if (null === $stack) {
return;
}

if (empty($stack->getRequestTarget())) {
$stack->setRequestTarget($request->getRequestTarget());
}
if (empty($stack->getRequestMethod())) {
$stack->setRequestMethod($request->getMethod());
}
if (empty($stack->getRequestScheme())) {
$stack->setRequestScheme($request->getUri()->getScheme());
}
if (empty($stack->getRequestHost())) {
$stack->setRequestHost($request->getUri()->getHost());
}
if (empty($stack->getClientRequest())) {
$stack->setClientRequest($this->formatter->formatRequest($request));
}
if (empty($stack->getCurlCommand())) {
$stack->setCurlCommand($this->formatter->formatAsCurlCommand($request));
}
}
}
130 changes: 130 additions & 0 deletions Tests/Functional/ProfilingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace Http\HttplugBundle\Tests\Functional;

use GuzzleHttp\Psr7\Request;
use Http\Client\Common\Plugin;
use Http\Client\Common\PluginClient;
use Http\Discovery\StreamFactoryDiscovery;
use Http\Discovery\UriFactoryDiscovery;
use Http\HttplugBundle\Collector\Collector;
use Http\HttplugBundle\Collector\Formatter;
use Http\HttplugBundle\Collector\ProfileClient;
use Http\HttplugBundle\Collector\ProfilePlugin;
use Http\HttplugBundle\Collector\StackPlugin;
use Http\Message\Formatter\CurlCommandFormatter;
use Http\Message\Formatter\FullHttpMessageFormatter;
use Http\Mock\Client;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Stopwatch\Stopwatch;

class ProfilingTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Collector
*/
private $collector;

/**
* @var Formatter
*/
private $formatter;

/**
* @var Stopwatch
*/
private $stopwatch;

public function setUp()
{
$this->collector = new Collector([]);
$this->formatter = new Formatter(new FullHttpMessageFormatter(), new CurlCommandFormatter());
$this->stopwatch = new Stopwatch();
}

public function testProfilingWithCachePlugin()
{
$client = $this->createClient([
new Plugin\CachePlugin(new ArrayAdapter(), StreamFactoryDiscovery::find(), [
'respect_response_cache_directives' => [],
'default_ttl' => 86400,
]),
]);

$client->sendRequest(new Request('GET', 'https://example.com'));
$client->sendRequest(new Request('GET', 'https://example.com'));

$this->assertCount(2, $this->collector->getStacks());
$stack = $this->collector->getStacks()[1];
$this->assertEquals('GET', $stack->getRequestMethod());
$this->assertEquals('https', $stack->getRequestScheme());
$this->assertEquals('/', $stack->getRequestTarget());
$this->assertEquals('example.com', $stack->getRequestHost());
}

public function testProfilingWhenPluginThrowException()
{
$client = $this->createClient([
new ExceptionThrowerPlugin(),
]);

$this->setExpectedException(\Exception::class);

try {
$client->sendRequest(new Request('GET', 'https://example.com'));
} finally {
$this->assertCount(1, $this->collector->getStacks());
$stack = $this->collector->getStacks()[0];
$this->assertEquals('GET', $stack->getRequestMethod());
$this->assertEquals('https', $stack->getRequestScheme());
$this->assertEquals('/', $stack->getRequestTarget());
$this->assertEquals('example.com', $stack->getRequestHost());
}
}

public function testProfiling()
{
$client = $this->createClient([
new Plugin\AddHostPlugin(UriFactoryDiscovery::find()->createUri('https://example.com')),
new Plugin\RedirectPlugin(),
new Plugin\RetryPlugin(),
]);

$client->sendRequest(new Request('GET', '/'));

$this->assertCount(1, $this->collector->getStacks());
$stack = $this->collector->getStacks()[0];
$this->assertCount(3, $stack->getProfiles());
$this->assertEquals('GET', $stack->getRequestMethod());
$this->assertEquals('https', $stack->getRequestScheme());
$this->assertEquals('/', $stack->getRequestTarget());
$this->assertEquals('example.com', $stack->getRequestHost());
}

private function createClient(array $plugins, $clientName = 'Acme', array $clientOptions = [])
{
$plugins = array_map(function (Plugin $plugin) {
return new ProfilePlugin($plugin, $this->collector, $this->formatter, get_class($plugin));
}, $plugins);

array_unshift($plugins, new StackPlugin($this->collector, $this->formatter, $clientName));

$client = new Client();
$client = new ProfileClient($client, $this->collector, $this->formatter, $this->stopwatch);
$client = new PluginClient($client, $plugins, $clientOptions);

return $client;
}
}

class ExceptionThrowerPlugin implements Plugin
{
/**
* {@inheritdoc}
*/
public function handleRequest(RequestInterface $request, callable $next, callable $first)
{
throw new \Exception();
}
}
24 changes: 20 additions & 4 deletions Tests/Unit/Collector/ProfilePluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use Http\HttplugBundle\Collector\ProfilePlugin;
use Http\HttplugBundle\Collector\Stack;
use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use Http\Promise\RejectedPromise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

Expand All @@ -36,6 +38,11 @@ class ProfilePluginTest extends \PHPUnit_Framework_TestCase
*/
private $response;

/**
* @var Promise
*/
private $fulfilledPromise;

/**
* @var Stack
*/
Expand All @@ -46,6 +53,11 @@ class ProfilePluginTest extends \PHPUnit_Framework_TestCase
*/
private $exception;

/**
* @var Promise
*/
private $rejectedPromise;

/**
* @var Formatter
*/
Expand All @@ -62,8 +74,10 @@ public function setUp()
$this->collector = $this->getMockBuilder(Collector::class)->disableOriginalConstructor()->getMock();
$this->request = new Request('GET', '/');
$this->response = new Response();
$this->fulfilledPromise = new FulfilledPromise($this->response);
$this->currentStack = new Stack('default', 'FormattedRequest');
$this->exception = new TransferException();
$this->rejectedPromise = new RejectedPromise($this->exception);
$this->formatter = $this->getMockBuilder(Formatter::class)->disableOriginalConstructor()->getMock();

$this->collector
Expand All @@ -74,9 +88,7 @@ public function setUp()
$this->plugin
->method('handleRequest')
->willReturnCallback(function ($request, $next, $first) {
$next($request);

return new FulfilledPromise($this->response);
return $next($request);
})
;

Expand Down Expand Up @@ -115,13 +127,15 @@ public function testCallDecoratedPlugin()
;

$this->subject->handleRequest($this->request, function () {
return $this->fulfilledPromise;
}, function () {
});
}

public function testProfileIsInitialized()
{
$this->subject->handleRequest($this->request, function () {
return $this->fulfilledPromise;
}, function () {
});

Expand All @@ -133,6 +147,7 @@ public function testProfileIsInitialized()
public function testCollectRequestInformations()
{
$this->subject->handleRequest($this->request, function () {
return $this->fulfilledPromise;
}, function () {
});

Expand All @@ -143,6 +158,7 @@ public function testCollectRequestInformations()
public function testOnFulfilled()
{
$promise = $this->subject->handleRequest($this->request, function () {
return $this->fulfilledPromise;
}, function () {
});

Expand All @@ -156,7 +172,7 @@ public function testOnRejected()
$this->setExpectedException(TransferException::class);

$promise = $this->subject->handleRequest($this->request, function () {
throw new TransferException();
return $this->rejectedPromise;
}, function () {
});

Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
"php-http/guzzle6-adapter": "^1.1.1",
"php-http/react-adapter": "^0.2.1",
"php-http/buzz-adapter": "^0.3",
"php-http/mock-client": "^1.0",
"symfony/phpunit-bridge": "^3.2",
"symfony/twig-bundle": "^2.8 || ^3.0",
"symfony/twig-bridge": "^2.8 || ^3.0",
"symfony/web-profiler-bundle": "^2.8 || ^3.0",
"symfony/finder": "^2.7 || ^3.0",
"symfony/cache": "^3.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. php-cache is not good enough for you? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jokes aside, can you require this on sf2.8?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes if you use PHP 5.5.9+

"polishsymfonycommunity/symfony-mocker-container": "^1.0",
"matthiasnoback/symfony-dependency-injection-test": "^1.0"
},
Expand Down