237 lines
6.2 KiB
Markdown
237 lines
6.2 KiB
Markdown
|
# HTTP Clients and zend-feed
|
||
|
|
||
|
Several operations in zend-feed's Reader subcomponent require an HTTP client:
|
||
|
|
||
|
- importing a feed
|
||
|
- finding links in a feed
|
||
|
|
||
|
In order to allow developers a choice in HTTP clients, the subcomponent defines
|
||
|
several interfaces and classes. Elsewhere in the documentation, we reference
|
||
|
where an HTTP client may be used; this document details what constitutes an HTTP
|
||
|
client and its behavior, and some of the concrete classes available within the
|
||
|
component for implementing this behavior.
|
||
|
|
||
|
## ClientInterface and HeaderAwareClientInterface
|
||
|
|
||
|
First, we define two interfaces for clients,
|
||
|
`Zend\Feed\Reader\Http\ClientInterface` and `HeaderAwareClientInterface`:
|
||
|
|
||
|
```php
|
||
|
namespace Zend\Feed\Reader\Http;
|
||
|
|
||
|
interface ClientInterface
|
||
|
{
|
||
|
/**
|
||
|
* Make a GET request to a given URL.
|
||
|
*
|
||
|
* @param string $url
|
||
|
* @return ResponseInterface
|
||
|
*/
|
||
|
public function get($url);
|
||
|
}
|
||
|
|
||
|
interface HeaderAwareClientInterface extends ClientInterface
|
||
|
{
|
||
|
/**
|
||
|
* Make a GET request to a given URL.
|
||
|
*
|
||
|
* @param string $url
|
||
|
* @param array $headers
|
||
|
* @return ResponseInterface
|
||
|
*/
|
||
|
public function get($url, array $headers = []);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The first is header-agnostic, and assumes that the client will simply perform an
|
||
|
HTTP GET request. The second allows providing headers to the client; typically,
|
||
|
these are used for HTTP caching headers. `$headers` must be in the following
|
||
|
structure:
|
||
|
|
||
|
```php
|
||
|
$headers = [
|
||
|
'X-Header-Name' => [
|
||
|
'header',
|
||
|
'values',
|
||
|
],
|
||
|
];
|
||
|
```
|
||
|
|
||
|
i.e., each key is a header name, and each value is an array of values for that
|
||
|
header. If the header represents only a single value, it should be an array with
|
||
|
that value:
|
||
|
|
||
|
```php
|
||
|
$headers = [
|
||
|
'Accept' => [ 'application/rss+xml' ],
|
||
|
];
|
||
|
```
|
||
|
|
||
|
A call to `get()` should yield a *response*.
|
||
|
|
||
|
## ResponseInterface and HeaderAwareResponseInterface
|
||
|
|
||
|
Responses are modeled using `Zend\Feed\Reader\Http\ResponseInterface` and
|
||
|
`HeaderAwareResponseInterface`:
|
||
|
|
||
|
```php
|
||
|
namespace Zend\Feed\Reader\Http;
|
||
|
|
||
|
class ResponseInterface
|
||
|
{
|
||
|
/**
|
||
|
* Retrieve the status code.
|
||
|
*
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getStatusCode();
|
||
|
|
||
|
/**
|
||
|
* Retrieve the response body contents.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getBody();
|
||
|
}
|
||
|
|
||
|
class HeaderAwareResponseInterface extends ResponseInterface
|
||
|
{
|
||
|
/**
|
||
|
* Retrieve a named header line.
|
||
|
*
|
||
|
* Retrieve a header by name; all values MUST be concatenated to a single
|
||
|
* line. If no matching header is found, return the $default value.
|
||
|
*
|
||
|
* @param string $name
|
||
|
* @param null|string $default
|
||
|
* @return string
|
||
|
public function getHeaderLine($name, $default = null);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Internally, `Reader` will typehint against `ClientInterface` for the bulk of
|
||
|
operations. In some cases, however, certain capabilities are only possible if
|
||
|
the response can provide headers (e.g., for caching); in such cases, it will
|
||
|
check the instance against `HeaderAwareResponseInterface`, and only call
|
||
|
`getHeaderLine()` if it matches.
|
||
|
|
||
|
## Response
|
||
|
|
||
|
zend-feed ships with a generic `ResponseInterface` implementation,
|
||
|
`Zend\Feed\Http\Response`. It implements `HeaderAwareResponseInterface`, and
|
||
|
defines the following constructor:
|
||
|
|
||
|
```php
|
||
|
namespace Zend\Feed\Reader\Http;
|
||
|
|
||
|
class Response implements HeaderAwareResponseInterface
|
||
|
{
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* @param int $statusCode Response status code
|
||
|
* @param string $body Response body
|
||
|
* @param array $headers Response headers, if available
|
||
|
*/
|
||
|
public function __construct($statusCode, $body, array $headers = []);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## PSR-7 Response
|
||
|
|
||
|
[PSR-7](http://www.php-fig.org/psr/psr-7/) defines a set of HTTP message
|
||
|
interfaces, but not a client interface. To facilitate wrapping an HTTP client
|
||
|
that uses PSR-7 messages, we provide `Zend\Feed\Reader\Psr7ResponseDecorator`:
|
||
|
|
||
|
```php
|
||
|
namespace Zend\Feed\Reader\Http;
|
||
|
|
||
|
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
||
|
|
||
|
class Psr7ResponseDecorator implements HeaderAwareResponseInterface
|
||
|
{
|
||
|
/**
|
||
|
* @param PsrResponseInterface $response
|
||
|
*/
|
||
|
public function __construct(PsrResponseInterface $response);
|
||
|
|
||
|
/**
|
||
|
* @return PsrResponseInterface
|
||
|
*/
|
||
|
public function getDecoratedResponse();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Clients can then take the PSR-7 response they receive, pass it to the decorator,
|
||
|
and return the decorator.
|
||
|
|
||
|
To use the PSR-7 response, you will need to add the PSR-7 interfaces to your
|
||
|
application, if they are not already installed by the client of your choice:
|
||
|
|
||
|
```bash
|
||
|
$ composer require psr/http-message
|
||
|
```
|
||
|
|
||
|
## zend-http
|
||
|
|
||
|
We also provide a zend-http client decorator,
|
||
|
`Zend\Feed\Reader\Http\ZendHttpClientDecorator`:
|
||
|
|
||
|
```php
|
||
|
namespace Zend\Feed\Reader\Http;
|
||
|
|
||
|
use Zend\Http\Client as HttpClient;
|
||
|
|
||
|
class ZendHttpClientDecorator implements HeaderAwareClientInterface
|
||
|
{
|
||
|
/**
|
||
|
* @param HttpClient $client
|
||
|
*/
|
||
|
public function __construct(HttpClient $client);
|
||
|
|
||
|
/**
|
||
|
* @return HttpClient
|
||
|
*/
|
||
|
public function getDecoratedClient();
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Its `get()` implementation returns a `Response` instance seeded from the
|
||
|
zend-http response returned, including status, body, and headers.
|
||
|
|
||
|
zend-http is the default implementation assumed by `Zend\Feed\Reader\Reader`,
|
||
|
but *is not installed by default*. You may install it using composer:
|
||
|
|
||
|
```bash
|
||
|
$ composer require zendframework/zend-http
|
||
|
```
|
||
|
|
||
|
## Providing a client to Reader
|
||
|
|
||
|
By default, `Zend\Feed\Reader\Reader` will lazy load a zend-http client. If you
|
||
|
have not installed zend-http, however, PHP will raise an error indicating the
|
||
|
class is not found!
|
||
|
|
||
|
As such, you have two options:
|
||
|
|
||
|
1. Install zend-http: `composer require zendframework/zend-http`.
|
||
|
2. Inject the `Reader` with your own HTTP client.
|
||
|
|
||
|
To accomplish the second, you will need an implementation of
|
||
|
`Zend\Feed\Reader\Http\ClientInterface` or `HeaderAwareClientInterface`, and an
|
||
|
instance of that implementation. Once you do, you can use the static method
|
||
|
`setHttpClient()` to inject it.
|
||
|
|
||
|
As an example, let's say you've created a PSR-7-based implementation named
|
||
|
`My\Http\Psr7FeedClient`. You could then do the following:
|
||
|
|
||
|
```php
|
||
|
use My\Http\Psr7FeedClient;
|
||
|
use Zend\Feed\Reader\Reader;
|
||
|
|
||
|
Reader::setHttpClient(new Psr7FeedClient());
|
||
|
```
|
||
|
|
||
|
Your client will then be used for all `import()` and `findFeedLinks()`
|
||
|
operations.
|