This PR was squashed before being merged into the 4.3-dev branch (closes #30413). Discussion ---------- [HttpClient][Contracts] introduce component and related contracts | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #28628 | License | MIT | Doc PR | - This PR introduces new `HttpClient` contracts and component. It makes no compromises between DX, performance, and design. Its surface should be very simple to use, while still flexible enough to cover most advanced use cases thanks to streaming+laziness. Common existing HTTP clients for PHP rely on PSR-7, which is complex and orthogonal to the way Symfony is designed. More reasons we need this in core are the [package principles](https://en.wikipedia.org/wiki/Package_principles): if we want to be able to keep our BC+deprecation promises, we have to build on more stable and more abstract dependencies than Symfony itself. And we need an HTTP client for e.g. Symfony Mailer or #27738. The existing state-of-the-art puts a quite high bar in terms of features we must support if we want any adoption. The code in this PR aims at implementing an even better HTTP client for PHP than existing ones, with more (useful) features and a better architecture. What a pitch :) Two full implementations are provided: - `NativeHttpClient` is based on the native "http" stream wrapper. It's the most portable one but relies on a blocking `fopen()`. - `CurlHttpClient` relies on the curl extension. It supports full concurrency and HTTP/2, including server push. Here are some examples that work with both clients. For simple cases, all the methods on responses are synchronous: ```php $client = new NativeHttpClient(); $response = $client->get('https://google.com'); $statusCode = $response->getStatusCode(); $headers = $response->getHeaders(); $content = $response->getContent(); ``` By default, clients follow redirects. On `3xx`, `4xx` or `5xx`, the `getHeaders()` and `getContent()` methods throw an exception, unless their `$throw` argument is set to `false`. This is part of the "failsafe" design of the component. Another example of this failsafe property is that broken dechunk or gzip streams always trigger an exception, unlike most other HTTP clients who can silently ignore the situations. An array of options allows adjusting the behavior when sending requests. They are documented in `HttpClientInterface`. When several responses are 1) first requested in batch, 2) then accessed via any of their public methods, requests are done concurrently while waiting for one. For more advanced use cases, when streaming is needed: Streaming the request body is possible via the "body" request option. Streaming the response content is done via client's `stream()` method: ```php $client = new CurlHttpClient(); $response = $client->request('GET', 'http://...'); $output = fopen('output.file', 'w'); foreach ($client->stream($response) as $chunk) { fwrite($output, $chunk->getContent()); } ``` The `stream()` method also works with multiple responses: ```php $client = new CurlHttpClient(); $pool = []; for ($i = 0; $i < 379; ++$i) { $uri = "https://http2.akamai.com/demo/tile-$i.png"; $pool[] = $client->get($uri); } $chunks = $client->stream($pool); foreach ($chunks as $response => $chunk) { // $chunk is a ChunkInterface object if ($chunk->isLast()) { $content = $response->getContent(); } } ``` The `stream()` method accepts a second `$timeout` argument: responses that are *inactive* for longer than the timeout will emit an empty chunk to signal it. Providing `0` as timeout allows monitoring responses in a non-blocking way. Implemented: - flexible contracts for HTTP clients - `fopen()` + `curl`-based clients with close feature parity - gzip compression enabled when possible - streaming multiple responses concurrently - `base_uri` option for scoped clients - progress callback with detailed info and able to cancel the request - more flexible options for precise behavior control - flexible timeout management allowing e.g. server sent events - public key pinning - auto proxy configuration via env vars - transparent IDN support - `HttpClient::create()` factory - extensive error handling, e.g. on broken dechunk/gzip streams - time stats, primary_ip and other info inspired from `curl_getinfo()` - transparent HTTP/2-push support with authority validation - `Psr18Client` for integration with libs relying on PSR-18 - free from memory leaks by avoiding circular references - fixed handling of redirects when using the `fopen`-based client - DNS cache pre-population with `resolve` option Help wanted (can be done after merge): - `FrameworkBundle` integration: autowireable alias + semantic configuration for default options - add `TraceableHttpClient` and integrate with the profiler - logger integration - add a mock client More ideas: - record/replay like CsaGuzzleBundle - use raw sockets instead of the HTTP stream wrapper - `cookie_jar` option - HTTP/HSTS cache - using the symfony CLI binary to test ssl-related options, HTTP/2-push, etc. - add "auto" mode to the "buffer" option, based on the content-type? or array of content-types to buffer - *etc.* Commits ------- |
||
---|---|---|
.. | ||
Bridge | ||
Bundle | ||
Component | ||
Contracts |