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
Add plugin documentation #30
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,118 @@ | ||
# Plugin System | ||
|
||
The plugin system will allow to manipulate requests and responses inside a `HttpClient`. | ||
The plugin system allows to look at requests and responses and replace them if needed, inside an `HttpClient`. | ||
|
||
TODO: finalize system and document. | ||
Using the `Http\Client\Plugin\PluginClient`, you can inject an `HttpClient`, or an `HttpAsyncClient`, and an array | ||
of plugins implementing the `Http\Client\Plugin\Plugin` interface. | ||
|
||
Each plugin can replace the `RequestInterface` sent or the `ResponseInterface` received. It can also change the behavior of a call, | ||
like retrying the request or emit another one when a redirection response was received. | ||
|
||
## Install | ||
|
||
Install the plugin client in your project with composer: | ||
|
||
``` bash | ||
composer require "php-http/plugins:^1.0" | ||
``` | ||
|
||
## Usage | ||
|
||
First you need to have some plugins: | ||
|
||
```php | ||
use Http\Client\Plugin\RetryPlugin; | ||
use Http\Client\Plugin\RedirectPlugin; | ||
|
||
$retryPlugin = new RetryPlugin(); | ||
$redirectPlugin = new RedirectPlugin(); | ||
``` | ||
|
||
Then you can create a `PluginClient`: | ||
|
||
```php | ||
use Http\Discovery\HttpClientDiscovery; | ||
use Http\Client\Plugin\PluginClient; | ||
|
||
... | ||
|
||
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [ | ||
$retryPlugin, | ||
$redirectPlugin | ||
]); | ||
``` | ||
|
||
You can use the plugin client like a classic `Http\Client\HttpClient` or `Http\Client\HttpAsyncClient` one: | ||
|
||
```php | ||
// Send a request | ||
$response = $pluginClient->sendRequest($request); | ||
|
||
// Send an asynchronous request | ||
$promise = $pluginClient->sendAsyncRequest($request); | ||
``` | ||
|
||
Go to the [tutorial](tutorial.md) to read more about using `HttpClient` and `HttpAsyncClient` | ||
|
||
## Available plugins | ||
|
||
Each plugin has its own configuration and dependencies, check the documentation for each of the available plugins: | ||
|
||
- [Authentication](plugins/authentication.md): Add authentication header on a request | ||
- [Cookie](plugins/cookie.md): Add cookies to request and save them from the response | ||
- [Encoding](plugins/encoding.md): Add support for receiving chunked, deflate or gzip response | ||
- [Error](plugins/error.md): Transform bad response (400 to 599) to exception | ||
- [Redirect](plugins/redirect.md): Follow redirection coming from 3XX responses | ||
- [Retry](plugins/retry.md): Retry a failed call | ||
- [Stopwatch](plugins/stopwatch.md): Log time of a request call by using [the Symfony Stopwatch component](http://symfony.com/doc/current/components/stopwatch.html) | ||
|
||
## Order of plugins | ||
|
||
When you inject an array of plugins into the `PluginClient`, the order of the plugins matters. | ||
|
||
During the request, plugins are called in the order they have in the array, from first to last plugin. Once a response has been received, | ||
they are called again in inverse order, from last to first. | ||
|
||
i.e. with the following code: | ||
|
||
```php | ||
use Http\Discovery\HttpClientDiscovery; | ||
use Http\Client\Plugin\PluginClient; | ||
use Http\Client\Plugin\RetryPlugin; | ||
use Http\Client\Plugin\RedirectPlugin; | ||
|
||
$retryPlugin = new RetryPlugin(); | ||
$redirectPlugin = new RedirectPlugin(); | ||
|
||
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [ | ||
$retryPlugin, | ||
$redirectPlugin | ||
]); | ||
``` | ||
|
||
The execution chain will look like this: | ||
|
||
``` | ||
Request ---> PluginClient ---> RetryPlugin ---> RedirectPlugin ---> HttpClient ---- | ||
| (processing call) | ||
Response <--- PluginClient <--- RetryPlugin <--- RedirectPlugin <--- HttpClient <--- | ||
``` | ||
|
||
In order to have correct behavior over the global process, you need to understand well each plugin used, | ||
and manage a correct order when passing the array to the `PluginClient` | ||
|
||
`RetryPlugin` will be best at the end to optimize the retry process, but it can also be good | ||
to have it as the first plugin, if one of the plugins is inconsistent and may need a retry. | ||
|
||
The recommended way to order plugins is the following rules: | ||
|
||
1. Plugins that modify the request should be at the beginning (like the `AuthenticationPlugin` or the `CookiePlugin`) | ||
2. Plugins which intervene in the workflow should be in the "middle" (like the `RetryPlugin` or the `RedirectPlugin`) | ||
3. Plugins which log information should be last (like the `LoggerPlugin` or the `HistoryPlugin`) | ||
|
||
However, there can be exceptions to these rules. For example, for security reasons you might not want to log the authentication header | ||
and chose to put the AuthenticationPlugin after the LoggerPlugin. | ||
|
||
## Implementing your own Plugin | ||
|
||
Read this [documentation](plugins/plugin-implementation.md) if you want to create your own Plugin. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Authentication Plugin | ||
|
||
TODO: explain the authentication plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Cookie Plugin | ||
|
||
TODO: explain the cookie plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Encoding Plugin | ||
|
||
TODO: explain the encoding plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Error Plugin | ||
|
||
TODO: explain the error plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Implementing your own Plugin | ||
|
||
When writing your own Plugin, you need to be aware that the `PluginClient` is async first. This means that every plugin must | ||
be written by respecting the `HttpAsyncClient` contract and returns a `Promise`. | ||
|
||
Each plugin must implement the `Http\Client\Plugin\Plugin` interface. | ||
|
||
This interface defines the `handleRequest` method that allows to modify behavior of the call: | ||
|
||
```php | ||
/** | ||
* handle the request and return the response coming from the next callable | ||
* | ||
* @param RequestInterface $request Request to use | ||
* @param callable $next Callback to call to have the request, it muse have the request as it first argument | ||
* @param callable $first First element in the plugin chain, used to to restart a request from the beginning | ||
* | ||
* @return Promise | ||
*/ | ||
public function handleRequest(RequestInterface $request, callable $next, callable $first); | ||
``` | ||
|
||
The `$request` comes from upstream. You can replace it and pass a new version downstream if you need. Always be aware that | ||
the request is immutable. | ||
|
||
The `$next` callable is the next plugin in the execution chain. When you need to call it, you must pass the `$request` | ||
as the first argument of this callable. | ||
|
||
``` | ||
public function handleRequest(RequestInterface $request, callable $next, callable $first) | ||
{ | ||
$newRequest = $request->withHeader('MyHeader', 'MyValue'); | ||
|
||
return $next($newRequest); | ||
} | ||
``` | ||
|
||
|
||
The `$first` callable is the first plugin in the execution. It allows you to completely reboot the execution chain, or send | ||
other request if needed, while still going through all the defined plugins. Like the `$next` callable, you must pass the `$request` | ||
as the first argument of this callable. | ||
|
||
``` | ||
public function handleRequest(RequestInterface $request, callable $next, callable $first) | ||
{ | ||
if ($someCondition) { | ||
$newRequest = new Request(); | ||
$promise = $first($newRequest); | ||
|
||
// Use the promise do some jobs ... | ||
} | ||
|
||
return $next($request); | ||
} | ||
``` | ||
|
||
In this example the condition is not superfluous, you need to have some way to not calling the $first callable each time or | ||
you will end up with a infinite execution loop. | ||
|
||
The `$next` and `$first` callable will return a `Promise`. You can manipulate the `ResponseInterface` or the `Exception` | ||
by using the `then` method of the promise. | ||
|
||
``` | ||
public function handleRequest(RequestInterface $request, callable $next, callable $first) | ||
{ | ||
$newRequest = $request->withHeader('MyHeader', 'MyValue'); | ||
|
||
return $next($request)->then(function (ResponseInterface $response) { | ||
return $response->withHeader('MyResponseHeader', 'value'); | ||
}, function (Exception $exception) { | ||
echo $exception->getMessage(); | ||
|
||
throw $exception; | ||
}); | ||
} | ||
``` | ||
|
||
Anyway it is always a good practice to read existing implementation inside the [plugin repository](https://github.com/php-http/plugins) to better understand the whole | ||
process. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Redirect Plugin | ||
|
||
TODO: explain the redirect plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Retry Plugin | ||
|
||
TODO: explain the retry plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Stopwatch Plugin | ||
|
||
TODO: explain the stopwatch plugin |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did we not say the client always throws the exception in the sync mode? and it can't throw exceptions in async. i am not sure this plugin would make sense.
i just looked at HttpClient.php and we just say
@throws Exception
without further specifying when the exception would happen. but BatchClient for example seems to assume exceptions being thrown.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exception in this case is more when connection or transfer, parsing failed. This plugin transforms response who was correctly received but has a status code between 400 and 599 (the status code list where it throws exception can be changed by configuring the plugin)
it correspond to this plugin in the plugin list : " Convert error responses to exceptions (throw exception when status is 4xx / 5xx)"
And i didn't talk about the throw part as it's depend if your are in sync or async mode.