Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All Notable changes to `mapudo/guzzle-bundle` will be documented in this file.

Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## [2.4.0] - 2019-05-22
### Added
- Added middleware to automatically append api key query parameters.
- Added more extensive description for middleware
### Changed
- Changed code style of middleware classes to fit with newest standards WITHOUT breaking BC
- Clean up readme

## [2.3.0] - 2019-04-12
### Added
- Added functionality to define channels when registering LogMiddleware. This allows a user, to inject a specific logger into the LogMiddleware dependent on the channel.
Expand Down
File renamed without changes.
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,15 @@ available to configure via the config.

```php
<?php
use GuzzleHttp\TransferStats;
use Symfony\Component\HttpFoundation\Request;

$client = $container->get('guzzle.client.test_client');
$moreOptions = ['on_stats' => function (\GuzzleHttp\TransferStats $stats) {
$moreOptions = ['on_stats' => static function (TransferStats $stats) {
echo $stats->getEffectiveUri() . "\n";
echo $stats->getTransferTime() . "\n";
}];
$client->requestAsync(\Symfony\Component\HttpFoundation\Request::METHOD_GET, '/path/without/base_uri', $moreOptions);
$client->requestAsync(Request::METHOD_GET, '/path/without/base_uri', $moreOptions);
```

**Note**: *Clear your cache after adding a new log handler or a middleware
Expand Down Expand Up @@ -174,6 +177,10 @@ For example, to write your own handler, take the response and request array out

```php
<?php
use Mapudo\Bundle\GuzzleBundle\Log\Model\Request;
use Mapudo\Bundle\GuzzleBundle\Log\Model\Response;

/** @noinspection PhpUndefinedVariableInspection */
if ($record['context']) {
$response = null;
if (!empty($record['context']['response'])) {
Expand All @@ -190,8 +197,43 @@ if ($record['context']) {
or just do what you want.

### Middleware
This bundle comes with two middleware services already implemented. One
to dispatch events and one to log with given handlers.
This bundle comes with multiple middleware services already implemented.

#### Event dispatch middleware
The event dispatch middleware fires two events. One before the request is sent and one after the request has been sent (i. e. when we have a response). You can hook into these events by listening for

* `guzzle_event.pre_transaction`
* `guzzle_event.post_transaction`

#### Log middleware
The log middleware receives a logger as an argument and will use this logger along with it's definition, i.e. in which channel to log, for which level etc. to log BOTH request and response data including duration times for a request.

### Authentication middleware
This bundle offers middleware that automatically add authentication data to a request.
#### Api key
If you make requests to an API that requires an API key for authentication you can register a service definition for the `Mapudo\Bundle\GuzzleBundle\Middleware\Authentication\ApiKeyMiddleware`

This middleware receives two arguments, while the second one is optional.

* First argument is the api key to use
* Second argument is the query parameter name that will be used for the key which defaults to `key`.

So let's say you register your middleware like this

```yaml
services:
guzzle.middleware.authentication.api_key:
class: Mapudo\Bundle\GuzzleBundle\Middleware\Authentication\ApiKeyMiddleware
arguments:
- 'totallySecretString'
- 'api_key'
tags:
- { name: guzzle.middleware, method: authenticate, client: test_client }
```

and a request to `https://api.google.com/search-results` is made, the Middleware would transform the URI to `https://api.google.com/search-results?api_key=totallySecretString`

You can also add your own authentication middleware by implementing the `AuthenticationMiddlewareInterface`

#### Add your own Middleware
The bundle supports registering Middlewares by using `__invoke()` or creating a custom
Expand Down
40 changes: 40 additions & 0 deletions Tests/Middleware/Authentication/ApiKeyMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);

namespace Mapudo\Bundle\GuzzleBundle\Tests\Middleware\Authentication;

use GuzzleHttp\Psr7\Request;
use Mapudo\Bundle\GuzzleBundle\Middleware\Authentication\ApiKeyMiddleware;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;

class ApiKeyMiddlewareTest extends TestCase
{
/** @var ApiKeyMiddleware */
private $apiKeyMiddleware;

protected function setUp()
{
$this->apiKeyMiddleware = new ApiKeyMiddleware(
'totallySecretApiKey',
'api_key'
);
}

public function testAuthenticate()
{
$handler = static function (RequestInterface $request) {
Assert::assertSame('api_key=totallySecretApiKey', $request->getUri()->getQuery());
Assert::assertSame(
'https://api.google.com/calendar?api_key=totallySecretApiKey',
(string)$request->getUri()
);
};

$request = new Request('GET', 'https://api.google.com/calendar');
$options = ['options_are_unimportant_here'];

$this->apiKeyMiddleware->authenticate()($handler)($request, $options);
}
}
38 changes: 38 additions & 0 deletions src/Middleware/Authentication/ApiKeyMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);

namespace Mapudo\Bundle\GuzzleBundle\Middleware\Authentication;

use Closure;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface;

final class ApiKeyMiddleware implements AuthenticationMiddlewareInterface
{
/** @var string */
private $apiKey;

/** @var string */
private $apiKeyParameterName;

public function __construct(string $apiKey, string $apiKeyParameterName = 'key')
{
$this->apiKey = $apiKey;
$this->apiKeyParameterName = $apiKeyParameterName;
}

public function authenticate(): Closure
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
$newRequest = $request->withUri(Uri::withQueryValue(
$request->getUri(),
$this->apiKeyParameterName,
$this->apiKey
));

return $handler($newRequest, $options);
};
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);

namespace Mapudo\Bundle\GuzzleBundle\Middleware\Authentication;

use Closure;

interface AuthenticationMiddlewareInterface
{
public function authenticate(): Closure;
}
12 changes: 0 additions & 12 deletions src/Middleware/EventDispatchMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,12 @@ class EventDispatchMiddleware
/** @var string */
protected $clientName;

/**
* EventDispatchMiddleware constructor.
*
* @param EventDispatcherInterface $eventDispatcher
* @param string $clientName
*/
public function __construct(EventDispatcherInterface $eventDispatcher, string $clientName)
{
$this->eventDispatcher = $eventDispatcher;
$this->clientName = $clientName;
}

/**
* Dispatches an event before the transaction is made (i.e. before the response is being gathered)
* and another event after the transaction is done (i.e. we now got the response)
*
* @return Closure
*/
public function dispatch(): Closure
{
return function (callable $handler) {
Expand Down
27 changes: 7 additions & 20 deletions src/Middleware/LogMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace Mapudo\Bundle\GuzzleBundle\Middleware;

use Closure;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use function GuzzleHttp\Promise\rejection_for;

/**
* Class LogMiddleware
Expand All @@ -32,33 +34,22 @@ class LogMiddleware
/** @var string|null */
protected $clientName;

/**
* LogMiddleware constructor.
*
* @param LoggerInterface $logger
* @param MessageFormatter $formatter
* @param NormalizerInterface $normalizer
*/
public function __construct(LoggerInterface $logger, MessageFormatter $formatter, NormalizerInterface $normalizer)
{
$this->logger = $logger;
$this->formatter = $formatter;
$this->normalizer = $normalizer;
}

/**
* Log each request
* @return \Closure
*/
public function log(): \Closure
public function log(): Closure
{
$logger = $this->logger;
$formatter = $this->formatter;

return function (callable $handler) use ($logger, $formatter) {
return function (RequestInterface $request, array $options) use ($handler, $logger, $formatter) {
$duration = null;
$options['on_stats'] = function (TransferStats $stats) use (&$duration) {
$options['on_stats'] = static function (TransferStats $stats) use (&$duration) {
$duration = $stats->getTransferTime();
};

Expand All @@ -77,19 +68,13 @@ function ($reason) use ($logger, $request, $formatter, &$duration) {
$message = $formatter->format($request, $response, $reason);
$logger->error($message, $this->buildContext($request, $response, $duration));

return \GuzzleHttp\Promise\rejection_for($reason);
return rejection_for($reason);
}
);
};
};
}

/**
* Sets the client name. Used to distinguish between clients.
*
* @param string $clientName
* @return LogMiddleware
*/
public function setClientName(string $clientName): LogMiddleware
{
$this->clientName = $clientName;
Expand All @@ -102,13 +87,15 @@ private function buildContext(
float $duration = null
): array {
$request->getBody()->rewind();
/** @noinspection PhpUnhandledExceptionInspection */
$context = [
'request' => $this->normalizer->normalize($request),
'client' => $this->clientName,
'duration' => $duration,
];

if ($response !== null) {
/** @noinspection PhpUnhandledExceptionInspection */
$context['response'] = $this->normalizer->normalize($response);
}

Expand Down