diff --git a/components/http_client.rst b/components/http_client.rst new file mode 100644 index 00000000000..2a99653a1ed --- /dev/null +++ b/components/http_client.rst @@ -0,0 +1,537 @@ +.. index:: + single: HttpClient + single: Components; HttpClient + +The HttpClient Component +======================== + + The HttpClient component is a low-level HTTP client with support for both + PHP stream wrappers and cURL. It also provides utilities to consume APIs. + +.. versionadded:: 4.3 + + The HttpClient component was introduced in Symfony 4.3. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/http-client + +.. include:: /components/require_autoload.rst.inc + +Basic Usage +----------- + +Use the :class:`Symfony\\Component\\HttpClient\\HttpClient` class to create the +low-level HTTP client that makes requests, like the following ``GET`` request:: + + use Symfony\Component\HttpClient\HttpClient; + + $httpClient = HttpClient::create(); + $response = $httpClient->request('GET', 'https://api.github.com/repos/symfony/symfony-docs'); + + $statusCode = $response->getStatusCode(); + // $statusCode = 200 + $contentType = $response->getHeaders()['content-type'][0]; + // $contentType = 'application/json' + $content = $response->getContent(); + // $content = '{"id":521583, "name":"symfony-docs", ...}' + $content = $response->toArray(); + // $content = ['id' => 521583, 'name' => 'symfony-docs', ...] + +Enabling cURL Support +--------------------- + +This component supports both the native PHP streams and cURL to make the HTTP +requests. Although both are interchangeable and provide the same features, +including concurrent requests, HTTP/2 is only supported when using cURL. + +``HttpClient::create()`` selects the cURL transport if the `cURL PHP extension`_ +is enabled and falls back to PHP streams otherwise. If you prefer to select +the transport explicitly, use the following classes to create the client:: + + use Symfony\Component\HttpClient\NativeHttpClient; + use Symfony\Component\HttpClient\CurlHttpClient; + + // uses native PHP streams + $httpClient = new NativeHttpClient(); + + // uses the cURL PHP extension + $httpClient = new CurlHttpClient(); + +When using this component in a full-stack Symfony application, this behavior is +not configurable and cURL will be used automatically if the cURL PHP extension +is installed and enabled. Otherwise, the native PHP streams will be used. + +Enabling HTTP/2 Support +----------------------- + +HTTP/2 is only supported when using the cURL-based transport and the libcurl +version is >= 7.36.0. If you meet these requirements, you can enable HTTP/2 +explicitly via the ``http_version`` option:: + + $httpClient = HttpClient::create(['http_version' => '2.0']); + +If you don't set the HTTP version explicitly, Symfony will use ``'2.0'`` only +when the request protocol is ``https://`` (and the cURL requirements mentioned +earlier are met). + +Making Requests +--------------- + +The client created with the ``HttpClient`` class provides a single ``request()`` +method to perform all kinds of HTTP requests:: + + $response = $httpClient->request('GET', 'https://...'); + $response = $httpClient->request('POST', 'https://...'); + $response = $httpClient->request('PUT', 'https://...'); + // ... + +Responses are always asynchronous, so they are ready as soon as the response +HTTP headers are received, instead of waiting to receive the entire response +contents:: + + $response = $httpClient->request('GET', 'http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso'); + + // code execution continues immediately; it doesn't wait to receive the response + // you can get the value of any HTTP response header + $contentType = $response->getHeaders()['content-type'][0]; + + // trying to get the response contents will block the execution until + // the full response contents are received + $contents = $response->getContent(); + +This component also supports :ref:`streaming responses ` +for full asynchronous applications. + +Authentication +~~~~~~~~~~~~~~ + +The HTTP client supports different authentication mechanisms. They can be +defined globally when creating the client (to apply it to all requests) and to +each request (which overrides any global authentication):: + + // Use the same authentication for all requests + $httpClient = HttpClient::create([ + // HTTP Basic authentication with only the username and not a password + 'auth_basic' => ['the-username'], + + // HTTP Basic authentication with a username and a password + 'auth_basic' => ['the-username', 'the-password'], + + // HTTP Bearer authentication (also called token authentication) + 'auth_bearer' => 'the-bearer-token', + ]); + + $response = $httpClient->request('GET', 'https://...', [ + // use a different HTTP Basic authentication only for this request + 'auth_basic' => ['the-username', 'the-password'], + + // ... + ]); + +Query String Parameters +~~~~~~~~~~~~~~~~~~~~~~~ + +You can either append them manually to the requested URL, or better, add them +as an associative array to the ``query`` option:: + + // it makes an HTTP GET request to https://httpbin.org/get?token=...&name=... + $response = $httpClient->request('GET', 'https://httpbin.org/get', [ + // these values are automatically encoded before including them in the URL + 'query' => [ + 'token' => '...', + 'name' => '...', + ], + ]); + +Headers +~~~~~~~ + +Use the ``headers`` option to define both the default headers added to all +requests and the specific headers for each request:: + + // this header is added to all requests made by this client + $httpClient = HttpClient::create(['headers' => [ + 'Accept-Encoding' => 'gzip', + ]]); + + // this header is only included in this request and overrides the value + // of the same header if defined globally by the HTTP client + $response = $httpClient->request('POST', 'https://...', [ + 'headers' => [ + 'Content-Type' => 'text/plain', + ], + ]); + +Uploading Data +~~~~~~~~~~~~~~ + +This component provides several methods for uploading data using the ``body`` +option. You can use regular strings, closures and resources and they'll be +processed automatically when making the requests:: + + $response = $httpClient->request('POST', 'https://...', [ + // defining data using a regular string + 'body' => 'raw data', + + // defining data using an array of parameters + 'body' => ['parameter1' => 'value1', '...'], + + // using a closure to generate the uploaded data + 'body' => function () { + // ... + }, + + // using a resource to get the data from it + 'body' => fopen('/path/to/file', 'r'), + ]); + +When uploading data with the ``POST`` method, if you don't define the +``Content-Type`` HTTP header explicitly, Symfony assumes that you're uploading +form data and adds the required +``'Content-Type: application/x-www-form-urlencoded'`` header for you. + +When uploading JSON payloads, use the ``json`` option instead of ``body``. The +given content will be JSON-encoded automatically and the request will add the +``Content-Type: application/json`` automatically too:: + + $response = $httpClient->request('POST', 'https://...', [ + 'json' => ['param1' => 'value1', '...'], + ]); + +Cookies +~~~~~~~ + +The HTTP client provided by this component is stateless but handling cookies +requires a stateful storage (because responses can update cookies and they must +be used for subsequent requests). That's why this component doesn't handle +cookies automatically. + +You can either handle cookies yourself using the ``Cookie`` HTTP header or use +the :doc:`BrowserKit component ` which provides this +feature and integrates seamlessly with the HttpClient component. + +Redirects +~~~~~~~~~ + +By default, the HTTP client follows redirects, up to a maximum of 20, when +making a request. Use the ``max_redirects`` setting to configure this behavior +(if the number of redirects is higher than the configured value, you'll get a +:class:`Symfony\\Component\\HttpClient\\Exception\\RedirectionException`):: + + $response = $httpClient->request('GET', 'https://...', [ + // 0 means to not follow any redirect + 'max_redirects' => 0, + ]); + +.. Concurrent Requests +.. ~~~~~~~~~~~~~~~~~~~ +.. +.. +.. TODO +.. +.. + +Processing Responses +-------------------- + +The response returned by all HTTP clients is an object of type +:class:`Symfony\\Contracts\\HttpClient\\ResponseInterface` which provides the +following methods:: + + $response = $httpClient->request('GET', 'https://...'); + + // gets the HTTP status code of the response + $statusCode = $response->getStatusCode(); + + // gets the HTTP headers as string[][] with the header names lower-cased + $headers = $response->getHeaders(); + + // gets the response body as a string + $content = $response->getContent(); + + // returns info coming from the transport layer, such as "response_headers", + // "redirect_count", "start_time", "redirect_url", etc. + $httpInfo = $response->getInfo(); + // you can get individual info too + $startTime = $response->getInfo('start_time'); + +.. _http-client-streaming-responses: + +Streaming Responses +~~~~~~~~~~~~~~~~~~~ + +Call to the ``stream()`` method of the HTTP client to get *chunks* of the +response sequentially instead of waiting for the entire response:: + + $url = 'https://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso'; + $response = $httpClient->request('GET', $url, [ + // optional: if you don't want to buffer the response in memory + 'buffer' => false, + // optional: to display details about the response progress + 'on_progress' => function (int $dlNow, int $dlSize, array $info): void { + // ... + }, + ]); + + // Responses are lazy: this code is executed as soon as headers are received + if (200 !== $response->getStatusCode()) { + throw new \Exception('...'); + } + + // get the response contents in chunk and save them in a file + // response chunks implement Symfony\Contracts\HttpClient\ChunkInterface + $fileHandler = fopen('/ubuntu.iso', 'w'); + foreach ($httpClient->stream($response) as $chunk) { + fwrite($fileHandler, $chunk->getContent();); + } + +Handling Exceptions +~~~~~~~~~~~~~~~~~~~ + +When the HTTP status code of the response is not in the 200-299 range (i.e. 3xx, +4xx or 5xx) your code is expected to handle it. If you don't do that, the +``getHeaders()`` and ``getContent()`` methods throw an appropriate exception:: + + // the response of this request will be a 403 HTTP error + $response = $httpClient->request('GET', 'https://httpbin.org/status/403'); + + // this code results in a Symfony\Component\HttpClient\Exception\ClientException + // because it doesn't check the status code of the response + $content = $response->getContent(); + + // pass FALSE as the optional argument to not throw an exception and + // return instead an empty string + $content = $response->getContent(false); + +Caching Requests and Responses +------------------------------ + +This component provides a special HTTP client via the +:class:`Symfony\\Component\\HttpClient\\CachingHttpClient` class to cache +requests and their responses. The actual HTTP caching is implemented using the +:doc:`HttpKernel component `, so make sure it's +installed in your application. + +.. +.. TODO: +.. Show some example of caching requests+responses +.. +.. + +Scoping Client +-------------- + +It's common that some of the HTTP client options depend on the URL of the +request (e.g. you must set some headers when making requests to GitHub API but +not for other hosts). If that's your case, this component provides a special +HTTP client via the :class:`Symfony\\Component\\HttpClient\\ScopingHttpClient` +class to autoconfigure the HTTP client based on the requested URL:: + + use Symfony\Component\HttpClient\HttpClient; + use Symfony\Component\HttpClient\ScopingHttpClient; + + $client = HttpClient::create(); + $httpClient = new ScopingHttpClient($client, [ + // the key is a regexp which must match the beginning of the request URL + 'https://api\.github\.com/' => [ + 'headers' => [ + 'Accept' => 'application/vnd.github.v3+json', + 'Authorization' => 'token '.$githubToken, + ], + ], + + // use a '*' wildcard to apply some options to all requests + '*' => [ + // ... + ] + ]); + +If the request URL is relative (because you use the ``base_uri`` option), the +scoping HTTP client can't make a match. That's why you can define a third +optional argument in its constructor which will be considered the default +regular expression applied to relative URLs:: + + // ... + + $httpClient = new ScopingHttpClient($client, [ + 'https://api\.github\.com/' => [ + 'base_uri' => 'https://api.github.com/', + // ... + ], + + '*' => [ + // ... + ] + ], + // this is the regexp applied to all relative URLs + 'https://api\.github\.com/' + ); + +PSR-7 and PSR-18 Compatibility +------------------------------ + +This component uses its own interfaces and exception classes different from the +ones defined in `PSR-7`_ (HTTP message interfaces) and `PSR-18`_ (HTTP Client). +However, it includes the :class:`Symfony\\Component\\HttpClient\\Psr18Client` +class, which is an adapter to turn a Symfony ``HttpClientInterface`` into a +PSR-18 ``ClientInterface``. + +Before using it in your application, run the following commands to install the +required dependencies: + +.. code-block:: terminal + + # installs the base ClientInterface + $ composer require psr/http-client + + # installs an efficient implementation of response and stream factories + # with autowiring aliases provided by Symfony Flex + $ composer require nyholm/psr7 + +Now you can make HTTP requests with the PSR-18 client as follows:: + + use Nyholm\Psr7\Factory\Psr17Factory; + use Symfony\Component\HttpClient\Psr18Client; + + $psr17Factory = new Psr17Factory(); + $psr18Client = new Psr18Client(); + + $url = 'https://symfony.com/versions.json'; + $request = $psr17Factory->createRequest('GET', $url); + $response = $psr18Client->sendRequest($request); + + $content = json_decode($response->getBody()->getContents(), true); + +Symfony Framework Integration +----------------------------- + +When using this component in a full-stack Symfony application, you can configure +multiple clients with different configurations and inject them into your services. + +Configuration +~~~~~~~~~~~~~ + +Use the ``framework.http_client`` key to configure the default HTTP client used +in the application. Check out the full +:ref:`http_client config reference ` to learn about all +the available config options: + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + max_redirects: 7 + max_host_connections: 10 + +If you want to define multiple HTTP clients, use this other expanded configuration: + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + http_clients: + crawler: + headers: [{ 'X-Powered-By': 'ACME App' }] + http_version: '1.0' + default: + max_host_connections: 10 + max_redirects: 7 + +Injecting the HTTP Client Into Services +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your application only defines one HTTP client, you can inject it into any +service by type-hinting a constructor argument with the +:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`:: + + use Symfony\Contracts\HttpClient\HttpClientInterface; + + class SomeService + { + private $httpClient; + + public function __construct(HttpClientInterface $httpClient) + { + $this->httpClient = $httpClient; + } + } + +If you have several clients, you must use any of the methods defined by Symfony +to ref:`choose a specific service `. Each client +has a unique service named after its configuration. + +.. code-block:: yaml + + # config/services.yaml + services: + # ... + + # whenever a service type-hints HttpClientInterface, inject the GitHub client + Symfony\Contracts\HttpClient\HttpClientInterface: '@api_client.github' + + # inject the HTTP client called 'crawler' into this argument of this service + App\Some\Service: + $someArgument: '@http_client.crawler' + +Testing HTTP Clients and Responses +---------------------------------- + +This component includes the ``MockHttpClient`` and ``MockResponse`` classes to +use them in tests that need an HTTP client which doesn't make actual HTTP +requests. + +The first way of using ``MockHttpClient`` is to configure the set of responses +to return using its constructor:: + + use Symfony\Component\HttpClient\MockHttpClient; + use Symfony\Component\HttpClient\Response\MockResponse; + + $responses = [ + new MockResponse($body1, $info1), + new MockResponse($body2, $info2), + ]; + + $client = new MockHttpClient($responses); + // responses are returned in the same order as passed to MockHttpClient + $response1 = $client->request('...'); // returns $responses[0] + $response2 = $client->request('...'); // returns $responses[1] + +Another way of using ``MockHttpClient`` is to pass a callback that generates the +responses dynamically when it's called:: + + use Symfony\Component\HttpClient\MockHttpClient; + use Symfony\Component\HttpClient\Response\MockResponse; + + $callback = function ($method, $url, $options) { + return new MockResponse('...'); + }; + + $client = new MockHttpClient($callback); + $response = $client->request('...'); // calls $callback to get the response + +The responses provided to the mock client don't have to be instances of +``MockResponse``. Any class implementing ``ResponseInterface`` will work (e.g. +``$this->createMock(ResponseInterface::class)``). + +However, using ``MockResponse`` allows simulating chunked responses and timeouts:: + + $body = function () { + yield 'hello'; + // empty strings are turned into timeouts so that they are easy to test + yield ''; + yield 'world'; + }; + + $mockResponse = new MockResponse($body()); + +.. _`cURL PHP extension`: https://php.net/curl +.. _`PSR-7`: https://www.php-fig.org/psr/psr-7/ +.. _`PSR-18`: https://www.php-fig.org/psr/psr-18/ diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 396df31d3d3..6c766f5f745 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -88,6 +88,33 @@ Configuration * `hinclude_default_template`_ * :ref:`path ` +* `http_client`_ + + * `auth_basic`_ + * `auth_bearer`_ + * `base_uri`_ + * `bindto`_ + * `buffer`_ + * `cafile`_ + * `capath`_ + * `capture_peer_cert_chain`_ + * `ciphers`_ + * `headers`_ + * `http_version`_ + * `local_cert`_ + * `local_pk`_ + * `max_host_connections`_ + * `max_redirects`_ + * `no_proxy`_ + * `passphrase`_ + * `peer_fingerprint`_ + * `proxy`_ + * `query`_ + * `resolve`_ + * `timeout`_ + * `verify_host`_ + * `verify_peer`_ + * `http_method_override`_ * `ide`_ * :ref:`lock ` @@ -626,6 +653,270 @@ path The path prefix for fragments. The fragment listener will only be executed when the request starts with this path. +.. _reference-http-client: + +http_client +~~~~~~~~~~~ + +If there's only one HTTP client defined in the app, you can configure it +directly under the ``framework.http_client`` option: + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + headers: [{ 'X-Powered-By': 'ACME App' }] + max_host_connections: 10 + max_redirects: 7 + +If the app defines multiple HTTP clients, you must give them a unique name and +define them under the type of HTTP client you are creating (``http_clients`` for +regular clients and ``api_clients`` for clients that include utilities to +consume APIs): + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + http_clients: + crawler: + # ... + default: + # ... + api_clients: + github: + # ... + +auth_basic +.......... + +**type**: ``array`` + +The username and password used to create the ``Authorization`` HTTP header +used in HTTP Basic authentication. The value of this option must follow the +format ``['username', 'password']``. + +auth_bearer +........... + +**type**: ``string`` + +The token used to create the ``Authorization`` HTTP header used in HTTP Bearer +authentication (also called token authentication). + +base_uri +........ + +**type**: ``string`` + +URI that is merged into relative URIs, following the rules explained in the +`RFC 3986`_ standard. This is useful when all the requests you make share a +common prefix (e.g. ``https://api.github.com/``) so you can avoid adding it to +every request. + +Here are some common examples of how ``base_uri`` merging works in practice: + +=================== ============== ====================== +``base_uri`` Relative URI Actual Requested URI +=================== ============== ====================== +http://foo.com /bar http://foo.com/bar +http://foo.com/foo /bar http://foo.com/bar +http://foo.com/foo bar http://foo.com/bar +http://foo.com/foo/ bar http://foo.com/foo/bar +http://foo.com http://baz.com http://baz.com +http://foo.com/?bar bar http://foo.com/bar +=================== ============== ====================== + +bindto +...... + +**type**: ``string`` + +A network interface name, IP address, a host name or a UNIX socket to use as the +outgoing network interface. + +buffer +...... + +**type**: ``boolean`` + +.. TODO: improve this useless description + +Indicates if the response should be buffered or not. + +cafile +...... + +**type**: ``string`` + +The path of the certificate authority file that contains one or more +certificates used to verify the other servers' certificates. + +capath +...... + +**type**: ``string`` + +The path to a directory that contains one or more certificate authority files. + +capture_peer_cert_chain +....................... + +**type**: ``boolean`` + +If ``true``, the response includes a ``peer_certificate_chain`` attribute with +the peer certificates (OpenSSL X.509 resources). + +ciphers +....... + +**type**: ``string`` + +A list of the names of the ciphers allowed for the SSL/TLS connections. They +can be separated by colons, commas or spaces (e.g. ``'RC4-SHA:TLS13-AES-128-GCM-SHA256'``). + +headers +....... + +**type**: ``array`` + +An associative array of the HTTP headers added before making the request. This +value must use the format ``['header-name' => header-value, ...]``. + +http_version +............ + +**type**: ``string`` | ``null`` **default**: ``null`` + +The HTTP version to use, typically ``'1.1'`` or ``'2.0'``. Leave it to ``null`` +to let Symfony select the best version automatically. + +local_cert +.......... + +**type**: ``string`` + +The path to a file that contains the `PEM formatted`_ certificate used by the +HTTP client. This is often combined with the ``local_pk`` and ``passphrase`` +options. + +local_pk +........ + +**type**: ``string`` + +The path of a file that contains the `PEM formatted`_ private key of the +certificate defined in the ``local_cert`` option. + +max_host_connections +.................... + +**type**: ``integer`` **default**: ``6`` + +Defines the maximum amount of simultaneously open connections to a single host +(considering a "host" the same as a "host name + port number" pair). This limit +also applies for proxy connections, where the proxy is considered to be the host +for which this limit is applied. + +max_redirects +............. + +**type**: ``integer`` **default**: ``20`` + +The maximum number of redirects to follow. Use ``0`` to not follow any +redirection. + +no_proxy +........ + +**type**: ``string`` | ``null`` **default**: ``null`` + +A comma separated list of hosts that do not require a proxy to be reached, even +if one is configured. Use the ``'*'`` wildcard to match all hosts and an empty +string to match none (disables the proxy). + +passphrase +.......... + +**type**: ``string`` + +The passphrase used to encrypt the certificate stored in the file defined in the +``local_cert`` option. + +peer_fingerprint +................ + +**type**: ``array`` + +When negotiating a TLS or SSL connection, the server sends a certificate +indicating its identity. A public key is extracted from this certificate and if +it does not exactly match any of the public keys provided in this option, the +connection is aborted before sending or receiving any data. + +The value of this option is an associative array of ``algorithm => hash`` +(e.g ``['pin-sha256' => '...']``). + +proxy +..... + +**type**: ``string`` | ``null`` + +The HTTP proxy to use to make the requests. Leave it to ``null`` to detect the +proxy automatically based on your system configuration. + +query +..... + +**type**: ``array`` + +An associative array of the query string values added to the URL before making +the request. This value must use the format ``['parameter-name' => parameter-value, ...]``. + +resolve +....... + +**type**: ``array`` + +A list of hostnames and their IP addresses to pre-populate the DNS cache used by +the HTTP client in order to avoid a DNS lookup for those hosts. This option is +useful both to improve performance and to make your tests easier. + +The value of this option is an associative array of ``domain => IP address`` +(e.g ``['symfony.com' => '46.137.106.254', ...]``). + +timeout +....... + +**type**: ``float`` **default**: depends on your PHP config + +Time, in seconds, to wait for a response. If the response takes longer, a +:class:`Symfony\\Component\\HttpClient\\Exception\\TransportException` is thrown. +Its default value is the same as the value of PHP's `default_socket_timeout`_ +config option. + +verify_host +........... + +**type**: ``boolean`` + +If ``true``, the certificate sent by other servers is verified to ensure that +their common name matches the host included in the URL. This is usually +combined with ``verify_peer`` to also verify the certificate authenticity. + +verify_peer +........... + +**type**: ``boolean`` + +If ``true``, the certificate sent by other servers when negotiating a TLS or SSL +connection is verified for authenticity. Authenticating the certificate is not +enough to be sure about the server, so you should combine this with the +``verify_host`` option. + profiler ~~~~~~~~ @@ -2410,3 +2701,6 @@ to know their differences. .. _`session.sid_length PHP option`: https://php.net/manual/session.configuration.php#ini.session.sid-length .. _`session.sid_bits_per_character PHP option`: https://php.net/manual/session.configuration.php#ini.session.sid-bits-per-character .. _`X-Robots-Tag HTTP header`: https://developers.google.com/search/reference/robots_meta_tag +.. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt +.. _`default_socket_timeout`: https://php.net/manual/en/filesystem.configuration.php#ini.default-socket-timeout +.. _`PEM formatted`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy