Skip to content

Commit 31cbd03

Browse files
committed
minor #11650 [HttpClient] improve doc again (nicolas-grekas)
This PR was merged into the 4.3 branch. Discussion ---------- [HttpClient] improve doc again Fixes all todos and more :) fixes #11530. Commits ------- f9339a3 [HttpClient] improve doc again
2 parents a48026b + f9339a3 commit 31cbd03

File tree

1 file changed

+177
-39
lines changed

1 file changed

+177
-39
lines changed

components/http_client.rst

Lines changed: 177 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ The HttpClient Component
1313

1414
The HttpClient component was introduced in Symfony 4.3.
1515

16-
.. TODO
17-
.. tell about implementation vs abstraction
18-
.. tell there are more options
19-
.. tell chunked + compression are supported out of the box
20-
2116
Installation
2217
------------
2318

@@ -71,17 +66,16 @@ When using this component in a full-stack Symfony application, this behavior is
7166
not configurable and cURL will be used automatically if the cURL PHP extension
7267
is installed and enabled. Otherwise, the native PHP streams will be used.
7368

74-
Enabling HTTP/2 Support
75-
-----------------------
69+
HTTP/2 Support
70+
--------------
7671

77-
HTTP/2 is only supported when using the cURL-based transport and the libcurl
78-
version is >= 7.36.0. If you meet these requirements, HTTP/2 will be used by
79-
default when the request protocol is ``https``. If you need it for ``http``,
80-
you must enable it explicitly via the ``http_version`` option::
72+
When requesting an ``https`` URL, HTTP/2 is enabled by default if libcurl >= 7.36
73+
is used. To force HTTP/2 for ``http`` URLs, you need to enable it explicitly via
74+
the ``http_version`` option::
8175

8276
$httpClient = HttpClient::create(['http_version' => '2.0']);
8377

84-
Support for HTTP/2 PUSH works out of the box when libcurl >= 7.61.0 is used with
78+
Support for HTTP/2 PUSH works out of the box when libcurl >= 7.61 is used with
8579
PHP >= 7.2.17 / 7.3.4: pushed responses are put into a temporary cache and are
8680
used when a subsequent request is triggered for the corresponding URLs.
8781

@@ -112,6 +106,11 @@ immediately instead of waiting to receive the response::
112106
This component also supports :ref:`streaming responses <http-client-streaming-responses>`
113107
for full asynchronous applications.
114108

109+
.. note::
110+
111+
HTTP compression and chunked transfer encoding are automatically enabled when
112+
both your PHP runtime and the remote server support them.
113+
115114
Authentication
116115
~~~~~~~~~~~~~~
117116

@@ -233,13 +232,12 @@ making a request. Use the ``max_redirects`` setting to configure this behavior
233232
'max_redirects' => 0,
234233
]);
235234

236-
.. Concurrent Requests
237-
.. ~~~~~~~~~~~~~~~~~~~
238-
..
239-
..
240-
.. TODO
241-
..
242-
..
235+
Advanced Options
236+
~~~~~~~~~~~~~~~~
237+
238+
The :class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface` defines all the
239+
options you might need to take full control of the way the request is performed,
240+
including progress monitoring, DSN pre-resolution, timeout, SSL parameters, etc.
243241

244242
Processing Responses
245243
--------------------
@@ -265,6 +263,12 @@ following methods::
265263
// you can get individual info too
266264
$startTime = $response->getInfo('start_time');
267265

266+
.. note::
267+
268+
``$response->getInfo()`` is non-blocking: it returns *live* information
269+
about the response. Some of them might not be know yet (e.g. ``http_code``)
270+
when you'll call it.
271+
268272
.. tip::
269273

270274
Call ``$response->getInfo('debug')`` to get detailed logs about the HTTP transaction.
@@ -317,6 +321,146 @@ When the HTTP status code of the response is in the 300-599 range (i.e. 3xx,
317321
// instead the original response content (even if it's an error message)
318322
$content = $response->getContent(false);
319323

324+
Concurrent Requests
325+
-------------------
326+
327+
Thanks to responses being lazy, requests are always managed concurrently.
328+
On a fast enough network, the following code makes 379 requests in less than
329+
half a second when cURL is used::
330+
331+
use Symfony\Component\HttpClient\CurlHttpClient;
332+
333+
$client = new CurlHttpClient();
334+
335+
$responses = [];
336+
337+
for ($i = 0; $i < 379; ++$i) {
338+
$uri = "https://http2.akamai.com/demo/tile-$i.png";
339+
$responses[] = $client->request('GET', $uri);
340+
}
341+
342+
foreach ($responses as $response) {
343+
$content = $response->getContent();
344+
// ...
345+
}
346+
347+
As you can read in the first "for" loop, requests are issued but are not consumed
348+
yet. That's the trick when concurrency is desired: requests should be sent
349+
first and be read later on. This will allow the client to monitor all pending
350+
requests while your code waits for a specific one, as done in each iteration of
351+
the above "foreach" loop.
352+
353+
Multiplexing Responses
354+
~~~~~~~~~~~~~~~~~~~~~~
355+
356+
If you look again at the snippet above, responses are read in requests' order.
357+
But maybe the 2nd response came back before the 1st? Fully asynchronous operations
358+
require being able to deal with the responses in whatever order they come back.
359+
360+
In order to do so, the ``stream()`` method of HTTP clients accepts a list of
361+
responses to monitor. As mentionned :ref:`previously <http-client-streaming-responses>`,
362+
this method yields response chunks as they arrive from the network. By replacing
363+
the "foreach" in the snippet with this one, the code becomes fully async::
364+
365+
foreach ($client->stream($responses) as $response => $chunk) {
366+
if ($chunk->isFirst()) {
367+
// headers of $response just arrived
368+
// $response->getHeaders() is now a non-blocking call
369+
} elseif ($chunk->isLast()) {
370+
// the full content of $response just completed
371+
// $response->getContent() is now a non-blocking call
372+
} else {
373+
// $chunk->getContent() will return a piece
374+
// of the response body that just arrived
375+
}
376+
}
377+
378+
.. tip::
379+
380+
Use the ``user_data`` option combined with ``$response->getInfo('user_data')``
381+
to track the identity of the responses in your foreach loops.
382+
383+
Dealing with Network Timeouts
384+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
385+
386+
This component allows dealing with both request and response timeouts.
387+
388+
A timeout can happen when e.g. DNS resolution takes too much time, when the TCP
389+
connection cannot be opened in the given time budget, or when the response
390+
content pauses for too long. This can be configured with the ``timeout`` request
391+
option::
392+
393+
// A TransportExceptionInterface will be issued if nothing
394+
// happens for 2.5 seconds when accessing from the $response
395+
$response = $client->request('GET', 'https://...', ['timeout' => 2.5]);
396+
397+
The ``default_socket_timeout`` PHP ini setting is used if the option is not set.
398+
399+
The option can be overriden by using the 2nd argument of the ``stream()`` method.
400+
This allows monitoring several responses at once and applying the timeout to all
401+
of them in a group. If all responses become inactive for the given duration, the
402+
method will yield a special chunk whose ``isTimeout()`` will return ``true``::
403+
404+
foreach ($client->stream($responses, 1.5) as $response => $chunk) {
405+
if ($chunk->isTimeout()) {
406+
// $response staled for more than 1.5 seconds
407+
}
408+
}
409+
410+
A timeout is not necessarily an error: you can decide to stream again the
411+
response and get remaining contents that might come back in a new timeout, etc.
412+
413+
.. tip::
414+
415+
Passing ``0`` as timeout allows monitoring responses in a non-blocking way.
416+
417+
.. note::
418+
419+
Timeouts control how long one is willing to wait *while the HTTP transation
420+
is idle*. Big responses can last as long as needed to complete, provided they
421+
remain active during the transfer and never pause for longer than specified.
422+
423+
Dealing with Network Errors
424+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
425+
426+
Network errors (broken pipe, failed DSN resolution, etc.) are thrown as instances
427+
of :class:`Symfony\\Contracts\\HttpClient\\Exception\\TransportExceptionInterface`.
428+
429+
First of all, you don't *have* to deal with them: letting errors bubble to your
430+
generic exception-handling stack might be really fine in most use cases.
431+
432+
If you want to handle them, here is what you need to know:
433+
434+
To catch errors, you need to wrap calls to ``$client->request()`` but also calls
435+
to any methods of the returned responses. This is because responses are lazy, so
436+
that network errors can happen when calling e.g. ``getStatusCode()`` too::
437+
438+
try {
439+
// both lines can potentially throw
440+
$response = $client->request(...);
441+
$headers = $response->getHeaders();
442+
// ...
443+
} catch (TransportExceptionInterface $e) {
444+
// ...
445+
}
446+
447+
.. note::
448+
449+
Because ``$response->getInfo()`` is non-blocking, it shouldn't throw by design.
450+
451+
When multiplexing responses, you can deal with errors for individual streams by
452+
catching ``TransportExceptionInterface`` in the foreach loop::
453+
454+
foreach ($client->stream($responses) as $response => $chunk) {
455+
try {
456+
if ($chunk->isLast()) {
457+
// ... do something with $response
458+
}
459+
} catch (TransportExceptionInterface $e) {
460+
// ...
461+
}
462+
}
463+
320464
Caching Requests and Responses
321465
------------------------------
322466

@@ -435,8 +579,9 @@ the available config options:
435579
framework:
436580
# ...
437581
http_client:
438-
max_redirects: 7
439582
max_host_connections: 10
583+
default_options:
584+
max_redirects: 7
440585
441586
If you want to define multiple HTTP clients, use this other expanded configuration:
442587

@@ -448,16 +593,16 @@ If you want to define multiple HTTP clients, use this other expanded configurati
448593
http_client:
449594
scoped_clients:
450595
crawler.client:
451-
headers: [{ 'X-Powered-By': 'ACME App' }]
596+
headers: { 'X-Powered-By': 'ACME App' }
452597
http_version: '1.0'
453598
some_api.client:
454-
max_redirects: 7
599+
max_redirects: 5
455600
456-
Injecting the HTTP Client Into Services
601+
Injecting the HTTP Client into Services
457602
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
458603

459-
If your application only defines one HTTP client, you can inject it into any
460-
service by type-hinting a constructor argument with the
604+
If your application only needs one HTTP client, you can inject the default one
605+
into any services by type-hinting a constructor argument with the
461606
:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`::
462607

463608
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -476,18 +621,11 @@ If you have several clients, you must use any of the methods defined by Symfony
476621
to ref:`choose a specific service <services-wire-specific-service>`. Each client
477622
has a unique service named after its configuration.
478623

479-
.. code-block:: yaml
480-
481-
# config/services.yaml
482-
services:
483-
# ...
484-
485-
# whenever a service type-hints HttpClientInterface, inject the GitHub client
486-
Symfony\Contracts\HttpClient\HttpClientInterface: '@some_api.client'
487-
488-
# inject the HTTP client called 'crawler' into this argument of this service
489-
App\Some\Service:
490-
$someArgument: '@crawler.client'
624+
Each scoped client also defines a corresponding named autowiring alias.
625+
If you use for example
626+
``Symfony\Contracts\HttpClient\HttpClientInterface $myApiClient``
627+
as the type and name of an argument, autowiring will inject the ``my_api.client``
628+
service into your autowired classes.
491629

492630
Testing HTTP Clients and Responses
493631
----------------------------------
@@ -496,8 +634,8 @@ This component includes the ``MockHttpClient`` and ``MockResponse`` classes to
496634
use them in tests that need an HTTP client which doesn't make actual HTTP
497635
requests.
498636

499-
The first way of using ``MockHttpClient`` is to configure the set of responses
500-
to return using its constructor::
637+
The first way of using ``MockHttpClient`` is to pass a list of responses to its
638+
constructor. These will be yielded in order when requests are made::
501639

502640
use Symfony\Component\HttpClient\MockHttpClient;
503641
use Symfony\Component\HttpClient\Response\MockResponse;

0 commit comments

Comments
 (0)