amphp/http-client
Asynchronous HTTP client for PHP built on Revolt with fibers and concurrency. Supports HTTP/1 & HTTP/2, concurrent requests, connection pooling, redirects, gzip/deflate decoding, streaming bodies, TLS by default, cookies/sessions, proxies, and custom methods—no ext/curl dependency.
## Getting Started
### Minimal Steps
1. **Installation**:
```bash
composer require amphp/http-client
Optionally, install nghttp2 for HTTP/2 performance:
pecl install nghttp2
First Request:
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
$client = HttpClientBuilder::buildDefault();
$response = await $client->request(new Request('https://httpbin.org/get'));
echo await $response->getBody()->buffer();
Key Files:
vendor/amphp/http-client/src/ for core classes.examples/ for practical use cases (e.g., concurrency/1-concurrent-fetch.php).tests/ for edge-case validation.use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
$client = HttpClientBuilder::buildDefault();
$requests = [
new Request('https://httpbin.org/get'),
new Request('https://httpbin.org/ip'),
new Request('https://httpbin.org/user-agent'),
];
$responses = await $client->requestAll($requests);
foreach ($responses as $response) {
echo await $response->getBody()->buffer();
}
Build Client:
$client = (new HttpClientBuilder)
->setConnectTimeout(5.0) // 5 seconds
->setTimeout(10.0) // 10 seconds for full request
->build();
Create Request:
$request = new Request('https://api.example.com/data', 'POST');
$request->setHeader('Content-Type', 'application/json');
$request->setBody(json_encode(['key' => 'value']));
Handle Response:
$response = await $client->request($request);
if ($response->getStatus() === 200) {
$data = json_decode(await $response->getBody()->buffer(), true);
}
$response = await $client->request(new Request('https://example.com/large-file'));
$stream = $response->getBody();
while (!$stream->isFinished()) {
$chunk = await $stream->read();
// Process chunk (e.g., save to disk)
}
use Amp\Http\Client\Interceptor\RetryRequests;
$client = (new HttpClientBuilder)
->intercept(new RetryRequests(3)) // Retry 3 times
->build();
use Amp\Http\Client\Interceptor\SetRequestHeader;
$client = (new HttpClientBuilder)
->intercept(new SetRequestHeader('X-API-Key', 'your-key-here'))
->build();
$client = (new HttpClientBuilder)
->setMaxConnections(100) // Reuse connections
->build();
use Amp\Http\Client\Request;
$request = new Request('https://httpbin.org/post', 'POST');
$request->setBody(http_build_query([
'username' => 'john',
'password' => 'secret',
]));
$request->setHeader('Content-Type', 'application/x-www-form-urlencoded');
Service Provider:
use Amp\Http\Client\HttpClientBuilder;
use Illuminate\Support\ServiceProvider;
class AmpHttpClientServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton('amp.http.client', function () {
return (new HttpClientBuilder)
->setConnectTimeout(5.0)
->build();
});
}
}
Facade (Optional):
// app/Facades/AmpHttp.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class AmpHttp extends Facade {
protected static function getFacadeAccessor() {
return 'amp.http.client';
}
}
Usage in Controllers:
use App\Facades\AmpHttp;
use Amp\Http\Client\Request;
class ApiController extends Controller {
public async function fetchData() {
$response = await AmpHttp::request(new Request('https://api.example.com/data'));
return response()->json(json_decode(await $response->getBody()->buffer(), true));
}
}
Blocking the Event Loop:
await before request() or getBody()->buffer() will block the event loop.await:
$response = await $client->request($request); // Correct
// vs.
$response = $client->request($request); // Blocks!
Memory Leaks with Large Responses:
buffer()) for large files (e.g., downloads) can exhaust memory.$stream = $response->getBody();
while (!$stream->isFinished()) {
$chunk = await $stream->read();
file_put_contents('output.bin', $chunk, FILE_APPEND);
}
Redirect Headers Lost:
$client = (new HttpClientBuilder)
->intercept(new SetRequestHeader('Authorization', 'Bearer token'))
->build();
HTTP/2 Without nghttp2:
nghttp2 is unavailable, reducing performance.nghttp2 or accept the fallback:
pecl install nghttp2
Timeout Misconfiguration:
setTimeout) may be too short for slow APIs.$client = (new HttpClientBuilder)
->setTimeout(30.0) // 30 seconds
->build();
Immutable Assumption (PSR-7):
Request and Response objects are mutable (unlike PSR-7), leading to unexpected side effects.$request = new Request('https://example.com');
$modifiedRequest = clone $request;
$modifiedRequest->setHeader('X-Custom', 'value');
Cookie Handling:
amphp/http-client-cookies.composer require amphp/http-client-cookies
use Amp\Http\Client\Interceptor\CookieHandler;
$client = (new HttpClientBuilder)
->intercept(new CookieHandler())
->build();
Log Requests/Responses:
Use LogHttpArchive to debug:
use Amp\Http\Client\EventListener\LogHttpArchive;
$client = (new HttpClientBuilder)
->listen(new LogHttpArchive('/tmp/http-client.har'))
->build();
View logs in HTTP Archive Viewer.
Check Connection Pooling: Monitor active connections with:
$client->getConnectionPool()->getConnectionCount();
Validate Headers:
Use getHeaders() to inspect headers:
$headers = $response->getHeaders();
dd($headers);
Handle Exceptions:
Wrap requests in try-catch:
try {
$response = await $client->request($request);
} catch (\Amp\Http\Client\Exception\ClientException $e) {
// Handle client errors (e.g., 4xx)
} catch (\Amp\Http\Client\Exception\ServerException $e) {
// Handle server errors (e.g., 5xx)
} catch (\Amp\Http\Client\Exception\ConnectionException $e) {
// Handle connection issues
}
ApplicationInterceptor or NetworkInterceptor for custom logic:
use Amp\Http\Client\Interceptor\ApplicationInterceptor;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
class CustomInterceptor implements ApplicationInterceptor {
public function onRequest(Request $request): void {
$request->addHeader('X-Custom', 'value');
}
public
How can I help you explore Laravel packages today?