scratch – Blame information for rev 87
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
87 | office | 1 | ======== |
2 | Adapters |
||
3 | ======== |
||
4 | |||
5 | Guzzle uses *adapters* to send HTTP requests. Adapters emit the lifecycle |
||
6 | events of requests, transfer HTTP requests, and normalize error handling. |
||
7 | |||
8 | Default Adapter |
||
9 | =============== |
||
10 | |||
11 | Guzzle will use the best possible adapter based on your environment. |
||
12 | |||
13 | If cURL is present, Guzzle will use the following adapters by default: |
||
14 | |||
15 | - ``GuzzleHttp\Adapter\Curl\MultiAdapter`` is used to transfer requests in |
||
16 | parallel. |
||
17 | - If ``allow_url_fopen`` is enabled, then a |
||
18 | ``GuzzleHttp\Adapter\StreamingProxyAdapter`` is added so that streaming |
||
19 | requests are sent using the PHP stream wrapper. If this setting is disabled, |
||
20 | then streaming requests are sent through a cURL adapter. |
||
21 | - If using PHP 5.5 or greater, then a ``GuzzleHttp\Adapter\Curl\CurlAdapter`` |
||
22 | is used to send serial requests. Otherwise, the |
||
23 | ``GuzzleHttp\Adapter\Curl\MultiAdapter`` is used for serial and parallel |
||
24 | requests. |
||
25 | |||
26 | If cURL is not installed, then Guzzle will use a |
||
27 | ``GuzzleHttp\Adapter\StreamingAdapter`` to send requests through PHP's |
||
28 | HTTP stream wrapper. ``allow_url_fopen`` must be enabled if cURL is not |
||
29 | installed on your system. |
||
30 | |||
31 | Creating an Adapter |
||
32 | =================== |
||
33 | |||
34 | Creating a custom HTTP adapter allows you to completely customize the way an |
||
35 | HTTP request is sent over the wire. In some cases, you might need to use a |
||
36 | different mechanism for transferring HTTP requests other than cURL or PHP's |
||
37 | stream wrapper. For example, you might need to use a socket because the version |
||
38 | of cURL on your system has an old bug, maybe you'd like to implement future |
||
39 | response objects, or you want to create a thread pool and send parallel |
||
40 | requests using pthreads. |
||
41 | |||
42 | The first thing you need to know about implementing custom adapters are the |
||
43 | responsibilities of an adapter. |
||
44 | |||
45 | Adapter Responsibilities |
||
46 | ------------------------ |
||
47 | |||
48 | Adapters use a ``GuzzleHttp\Adapter\TransactionInterface`` which acts as a |
||
49 | mediator between ``GuzzleHttp\Message\RequestInterface`` and |
||
50 | ``GuzzleHttp\Message\ResponseInterface`` objects. The main goal of an adapter |
||
51 | is to set a response on the provided transaction object. |
||
52 | |||
53 | 1. The adapter MUST return a ``GuzzleHttp\Message\ResponseInterface`` object in |
||
54 | a successful condition. |
||
55 | |||
56 | 2. When preparing requests, adapters MUST properly handle as many of the |
||
57 | following request configuration options as possible: |
||
58 | |||
59 | - :ref:`cert-option` |
||
60 | - :ref:`connect_timeout-option` |
||
61 | - :ref:`debug-option` |
||
62 | - :ref:`expect-option` |
||
63 | - :ref:`proxy-option` |
||
64 | - :ref:`save_to-option` |
||
65 | - :ref:`ssl_key-option` |
||
66 | - :ref:`stream-option` |
||
67 | - :ref:`timeout-option` |
||
68 | - :ref:`verify-option` |
||
69 | - :ref:`decode_content` - When set to ``true``, the adapter must attempt to |
||
70 | decode the body of a ``Content-Encoding`` response (e.g., gzip). |
||
71 | |||
72 | 3. Adapters SHOULD not follow redirects. In the normal case, redirects are |
||
73 | followed by ``GuzzleHttp\Subscriber\Redirect``. Redirects SHOULD be |
||
74 | implemented using Guzzle event subscribers, not by an adapter. |
||
75 | |||
76 | 4. The adapter MUST emit a ``before`` event with a |
||
77 | ``GuzzleHttp\Event\BeforeEvent`` object before sending a request. If the |
||
78 | event is intercepted and a response is associated with a transaction during |
||
79 | the ``before`` event, then the adapter MUST not send the request over the |
||
80 | wire, but rather return the response. |
||
81 | |||
82 | 5. When all of the headers of a response have been received, the adapter MUST |
||
83 | emit a ``headers`` event with a ``GuzzleHttp\Event\HeadersEvent``. This |
||
84 | event MUST be emitted before any data is written to the body of the response |
||
85 | object. It is important to keep in mind that event listeners MAY mutate a |
||
86 | response during the emission of this event. |
||
87 | |||
88 | 6. The adapter MUST emit a ``complete`` event with a |
||
89 | ``GuzzleHttp\Event\CompleteEvent`` when a request has completed sending. |
||
90 | Adapters MUST emit the complete event for all valid HTTP responses, |
||
91 | including responses that resulted in a non 2xx level response. |
||
92 | |||
93 | 7. The adapter MUST emit an ``error`` event with a |
||
94 | ``GuzzleHttp\Event\ErrorEvent``when an error occurs during the transfer. |
||
95 | This includes when preparing a request for transfer, during the ``before`` |
||
96 | event, during the ``headers`` event, during the ``complete`` event, when |
||
97 | a networking error occurs, and so on. |
||
98 | |||
99 | 8. After emitting the ``error`` event, the adapter MUST check if the |
||
100 | error event was intercepted and a response was associated with the |
||
101 | transaction. If the propagation of the ``error`` event was not stopped, then |
||
102 | the adapter MUST throw the exception. If the propagation was stopped, then |
||
103 | the adapter MUST NOT throw the exception. |
||
104 | |||
105 | Parallel Adapters |
||
106 | ----------------- |
||
107 | |||
108 | Parallel adapters are used when using a client's ``sendAll()`` method. Parallel |
||
109 | adapters are expected to send one or more transactions in parallel. Parallel |
||
110 | adapters accept an ``\Iterator`` that yields |
||
111 | ``GuzzleHttp\Adapter\TransactionInterface`` object. In addition to the |
||
112 | iterator, the adapter is also provided an integer representing the number of |
||
113 | transactions to execute in parallel. |
||
114 | |||
115 | Parallel adapters are similar to adapters (described earlier), except for the |
||
116 | following: |
||
117 | |||
118 | 1. RequestExceptions are only thrown from a parallel adapter when the |
||
119 | ``GuzzleHttp\Exception\RequestException::getThrowImmediately()`` method of |
||
120 | an encountered exception returns ``true``. If this method does not return |
||
121 | ``true`` or the exception is not an instance of RequestException, then the |
||
122 | parallel adapter MUST NOT throw the exception. Error handling for parallel |
||
123 | transfers should normally be handled through event listeners that use |
||
124 | ``error`` events. |
||
125 | |||
126 | 2. Parallel adapters are not expected to return responses. Because parallel |
||
127 | adapters can, in theory, send an infinite number of requests, developers |
||
128 | must use event listeners to receive the ``complete`` event and handle |
||
129 | responses accordingly. |
||
130 | |||
131 | Emitting Lifecycle Events |
||
132 | ------------------------- |
||
133 | |||
134 | Request lifecycle events MUST be emitted by adapters and parallel adapters. |
||
135 | These lifecycle events are used by event listeners to modify requests, modify |
||
136 | responses, perform validation, and anything else required by an application. |
||
137 | |||
138 | Emitting request lifecycle events in an adapter is much simpler if you use the |
||
139 | static helper method of ``GuzzleHttp\Event\RequestEvents``. These methods are |
||
140 | used by the built-in in curl and stream wrapper adapters of Guzzle, so you |
||
141 | should use them too. |
||
142 | |||
143 | Example Adapter |
||
144 | =============== |
||
145 | |||
146 | Here's a really simple example of creating a custom HTTP adapter. For |
||
147 | simplicity, this example uses a magic ``send_request()`` function. |
||
148 | |||
149 | .. code-block:: php |
||
150 | |||
151 | <?php |
||
152 | |||
153 | namespace MyProject\Adapter; |
||
154 | |||
155 | use GuzzleHttp\Event\RequestEvents; |
||
156 | use GuzzleHttp\Event\HeadersEvent; |
||
157 | use GuzzleHttp\Message\MessageFactoryInterface; |
||
158 | |||
159 | class MyAdapter implements AdapterInterface |
||
160 | { |
||
161 | private $messageFactory; |
||
162 | |||
163 | public function __construct(MessageFactoryInterface $messageFactory) |
||
164 | { |
||
165 | $this->messageFactory = $messageFactory; |
||
166 | } |
||
167 | |||
168 | public function send(TransactionInterface $transaction) |
||
169 | { |
||
170 | RequestEvents::emitBefore($transaction); |
||
171 | |||
172 | // Check if the transaction was intercepted |
||
173 | if (!$transaction->getResponse()) { |
||
174 | // It wasn't intercepted, so send the request |
||
175 | $this->getResponse($transaction); |
||
176 | } |
||
177 | |||
178 | // Adapters always return a response in the successful case. |
||
179 | return $transaction->getResponse(); |
||
180 | } |
||
181 | |||
182 | private function getResponse(TransactionInterface $transaction) |
||
183 | { |
||
184 | $request = $transaction->getRequest(); |
||
185 | |||
186 | $response = send_request( |
||
187 | $request->getMethod(), |
||
188 | $request->getUrl(), |
||
189 | $request->getHeaders(), |
||
190 | $request->getBody() |
||
191 | ); |
||
192 | |||
193 | if ($response) { |
||
194 | $this->processResponse($response, $transaction); |
||
195 | } else { |
||
196 | // Emit the error event which allows listeners to intercept |
||
197 | // the error with a valid response. If it is not intercepted, |
||
198 | // a RequestException is thrown. |
||
199 | RequestEvents::emitError($transaction, $e); |
||
200 | } |
||
201 | } |
||
202 | |||
203 | private function processResponse( |
||
204 | array $response, |
||
205 | TransactionInterface $transaction |
||
206 | ) { |
||
207 | // Process the response, create a Guzzle Response object, and |
||
208 | // associate the response with the transaction. |
||
209 | $responseObject = $this->messageFactory->createResponse( |
||
210 | $response['status_code'], |
||
211 | $response['headers'] |
||
212 | ); |
||
213 | |||
214 | $transaction->setResponse($responseObject); |
||
215 | |||
216 | // Emit the headers event before downloading the body |
||
217 | RequestEvents::emitHeaders($transaction); |
||
218 | |||
219 | if ($response['body']) { |
||
220 | // Assuming the response body is a stream or something, |
||
221 | // associate it with the response object. |
||
222 | $responseObject->setBody(Stream::factory($response['body'])); |
||
223 | } |
||
224 | |||
225 | // Emit the complete event |
||
226 | RequestEvents::emitComplete($transaction); |
||
227 | } |
||
228 | } |