HTTP client
Examples
Every example of this topic, with its output.
Request#
$request = new Request('POST', '/search?q=phasync', new StringStream('{"limit":10}'), [
'Host' => 'example.com',
'Content-Type' => 'application/json',
]);
(string) $request->getUri(); // "http://example.com/search?q=phasync"Response#
$response = new Response(404, ['Content-Type' => 'text/plain'], new StringStream('Not found'));
$response->getStatusCode(); // 404
$response->getReasonPhrase(); // "Not Found"
$stream = new UnbufferedStream();
$response = $response->withBody($stream);ServerRequest#
$request = new ServerRequest('POST', '/login?next=/home', new StringStream('user=ann'), ['Host' => 'example.com'], parsedBody: ['user' => 'ann']);
$request->getQueryParams(); // ['next' => '/home']
$request->getParsedBody(); // ['user' => 'ann']Uri#
$uri = new Uri('https://example.com/search?q=phasync');
$uri->getHost(); // "example.com"
(string) $uri->withPath('/docs'); // "https://example.com/docs?q=phasync"ClientException#
use phasync\HttpClient\ClientException;
use phasync\HttpClient\HttpClient;
use phasync\Psr\Request;
use Psr\Http\Client\ClientExceptionInterface;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient(['maxRedirs' => 3]);
try {
$client->sendRequest(Request::create('GET', "$base/loop")); // redirects to itself
} catch (ClientException $e) {
echo get_class($e), "\n";
var_dump($e instanceof ClientExceptionInterface);
echo 'request: ', $e->getRequest()->getMethod(), ' ', $e->getRequest()->getUri()->getPath(), "\n";
}Output
phasync\HttpClient\ClientException bool(true) request: GET /loop
ClientException#
try {
$client->sendRequest($request);
} catch (\Psr\Http\Client\ClientExceptionInterface $e) {
echo $e->getMessage(), ' (cURL error ', $e->getCode(), ')';
}CurlResponse#
use phasync\HttpClient\HttpClient;
$base = require __DIR__ . '/../server.php';
$response = (new HttpClient())->get("$base/stream");
echo get_class($response), "\n";
echo $response->getStatusCode(), "\n"; // waits for the headers only
$body = $response->getBody();
while (!$body->eof()) {
$chunk = $body->read(1024); // what has arrived so far
if ('' !== $chunk) {
echo 'read: ', json_encode($chunk), "\n";
}
}
echo 'downloaded bytes: ', $response->getDownloadedBytes(), "\n";Output
phasync\HttpClient\CurlResponse 200 read: "line 1\nline 2\nline 3\n" downloaded bytes: 21
CurlResponse#
$response = $client->get('https://example.com/report.csv');
if (200 === $response->getStatusCode()) {
$body = $response->getBody();
while (!$body->eof()) {
echo $body->read(8192);
}
}CurlResponse::getStatusCode#
try {
$status = $client->get('https://example.com/')->getStatusCode();
} catch (\Psr\Http\Client\NetworkExceptionInterface $e) {
// connection refused, DNS failure, timeout: $e->getCode() is cURL's error number
}CurlResponse::getHeaderLine#
echo $client->get('https://example.com/')->getHeaderLine('content-type');CurlResponse::getBody#
$json = \json_decode((string) $client->get('https://example.com/data.json')->getBody(), true);CurlResponse::getDownloadedBytes#
$response = $client->get('https://example.com/big.iso');
$body = $response->getBody();
while (!$body->eof()) {
$body->read(65536);
echo $response->getDownloadedBytes(), " of ", $response->getDownloadSize(), "\n";
}HttpClient#
use phasync\HttpClient\HttpClient;
$base = require __DIR__ . '/../server.php'; // a local server for the example
$client = new HttpClient(['timeoutMs' => 5000]);
// Neither transfer has started yet: both start together at the first read
$slow = $client->get("$base/slow?seconds=0.2&name=slow");
$fast = $client->get("$base/slow?seconds=0.1&name=fast");
echo $slow->getStatusCode(), ' ', $slow->getBody(), "\n";
echo $fast->getStatusCode(), ' ', $fast->getBody(), "\n";Output
200 slow 200 fast
HttpClient#
use phasync\HttpClient\HttpClient;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient();
// Inside phasync::run() a request suspends only the coroutine that waits for it
phasync::run(function () use ($client, $base) {
foreach (['b' => 0.2, 'a' => 0.1] as $name => $seconds) {
phasync::go(function () use ($client, $base, $name, $seconds) {
$body = (string) $client->get("$base/slow?seconds=$seconds&name=$name")->getBody();
echo "response $body\n";
});
}
echo "both requests sent\n";
});Output
both requests sent response a response b
HttpClient#
use phasync\HttpClient\HttpClient;
$client = new HttpClient(['timeoutMs' => 5000]);
// Neither transfer has started yet, so both run at the same time once the first is read.
$a = $client->get('https://example.com/a');
$b = $client->get('https://example.com/b');
echo $a->getStatusCode(), "\n";
echo $b->getBody();
// PSR-18: returns when the headers have arrived, or throws.
try {
$response = $client->sendRequest(\phasync\Psr\Request::create('GET', 'https://example.com/c'));
} catch (\Psr\Http\Client\NetworkExceptionInterface $e) {
echo 'no response: ', $e->getMessage();
}HttpClient::__construct#
$client = new HttpClient([
'userAgent' => 'my-app/1.0',
'timeoutMs' => 5000,
'maxRedirs' => 5,
]);HttpClient::addMiddlewareFunction#
use phasync\HttpClient\HttpClient;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient();
$client->addMiddlewareFunction(function (RequestInterface $request, ClientInterface $next): ResponseInterface {
return $next->sendRequest($request->withHeader('X-Request-Id', 'req-1'));
});
// Added last, so it runs first: it sees the request before the header is added
$client->addMiddlewareFunction(function (RequestInterface $request, ClientInterface $next): ResponseInterface {
$response = $next->sendRequest($request);
echo 'log: ', $request->getMethod(), ' ', $request->getUri()->getPath(), ' -> ', $response->getStatusCode(), "\n";
return $response;
});
$echo = json_decode((string) $client->get("$base/echo")->getBody(), true);
echo 'the server got X-Request-Id: ', $echo['headers']['x-request-id'], "\n";Output
log: GET /echo -> 200 the server got X-Request-Id: req-1
HttpClient::addMiddlewareFunction#
$client->addMiddlewareFunction(
function (RequestInterface $request, ClientInterface $next): ResponseInterface {
return $next->sendRequest($request->withHeader('X-Request-Id', \bin2hex(\random_bytes(8))));
}
);HttpClient::sendRequest#
use phasync\HttpClient\HttpClient;
use phasync\Psr\Request;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient();
$response = $client->sendRequest(Request::create('GET', "$base/hello"));
echo $response->getStatusCode(), ' ', $response->getReasonPhrase(), "\n";
echo $response->getHeaderLine('Content-Type'), "\n";
echo $response->getBody();
// An error status is a response like any other: nothing is thrown
$response = $client->sendRequest(Request::create('GET', "$base/status/503"));
echo $response->getStatusCode(), ' ', $response->getReasonPhrase(), "\n";Output
200 OK text/plain;charset=UTF-8 Hello, world 503 Service Unavailable
HttpClient::sendRequest#
use phasync\Psr\Request;
try {
$response = $client->sendRequest(Request::create('GET', 'https://example.com/'));
echo $response->getStatusCode();
} catch (\Psr\Http\Client\NetworkExceptionInterface $e) {
// connection refused, DNS failure, TLS failure, timeout, connection reset
}HttpClient::sendAsyncRequest#
use phasync\HttpClient\HttpClient;
use phasync\Psr\Request;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient();
$responses = [];
foreach (['one', 'two', 'three'] as $name) {
$responses[$name] = $client->sendAsyncRequest(Request::create('GET', "$base/slow?seconds=0.1&name=$name"));
}
// The first read starts all three transfers
foreach ($responses as $name => $response) {
echo "$name: ", $response->getBody(), "\n";
}Output
one: one two: two three: three
HttpClient::sendAsyncRequest#
use phasync\Psr\Request;
$a = $client->sendAsyncRequest(Request::create('GET', 'https://example.com/a'));
$b = $client->sendAsyncRequest(Request::create('GET', 'https://example.com/b'));
echo $a->getStatusCode(), ' ', $b->getStatusCode(); // both transfers run togetherHttpClient::request#
use phasync\HttpClient\HttpClient;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient();
// Array data is encoded as JSON when the Content-Type header says so
$response = $client->request('PATCH', "$base/echo", ['done' => true], [
'headers' => ['Content-Type: application/json', 'Authorization: Bearer secret-token'],
]);
$echo = json_decode((string) $response->getBody(), true);
echo $echo['method'], ' ', $echo['body'], "\n";
echo $echo['headers']['authorization'], "\n";
// For GET, the data goes in the query string
$response = $client->request('GET', "$base/echo", ['q' => 'phasync', 'page' => 2]);
echo json_encode(json_decode((string) $response->getBody(), true)['query']), "\n";Output
PATCH {"done":true}
Bearer secret-token
{"q":"phasync","page":"2"}HttpClient::request#
$response = $client->request('DELETE', 'https://example.com/items/7', null, [
'headers' => ['Authorization: Bearer ' . $token],
]);
echo $response->getStatusCode();
$response = $client->request('PATCH', 'https://example.com/items/7', ['done' => true], [
'headers' => ['Content-Type: application/json'],
]);HttpClient::get#
$response = $client->get('https://example.com/search?q=phasync', ['timeoutMs' => 2000]);
$data = \json_decode((string) $response->getBody(), true);HttpClient::post#
use phasync\HttpClient\HttpClient;
$base = require __DIR__ . '/../server.php';
$client = new HttpClient();
// An array is sent form-encoded unless a Content-Type header says otherwise
$response = $client->post("$base/echo", ['name' => 'Ada', 'tags' => ['math', 'engines']]);
$echo = json_decode((string) $response->getBody(), true);
echo $echo['headers']['content-type'], "\n";
echo json_encode($echo['form']), "\n";Output
application/x-www-form-urlencoded
{"name":"Ada","tags":["math","engines"]}HttpClient::post#
$response = $client->post('https://example.com/login', ['user' => 'frode', 'password' => $secret]);
$response = $client->post('https://example.com/api', ['name' => 'x'], [
'headers' => ['Content-Type: application/json'],
]);HttpClient::put#
$response = $client->put('https://example.com/items/7', \json_encode(['name' => 'x']), [
'headers' => ['Content-Type: application/json'],
]);HttpClientOptions#
use phasync\HttpClient\HttpClient;
use phasync\HttpClient\HttpClientOptions;
$base = require __DIR__ . '/../server.php';
$defaults = new HttpClientOptions();
$defaults->userAgent = 'inventory-sync/1.0';
$defaults->timeoutMs = 5000;
$client = new HttpClient($defaults);
$echo = json_decode((string) $client->get("$base/echo")->getBody(), true);
echo $echo['headers']['user-agent'], "\n";
// Options given to one request apply on top of the defaults, for that request only
$echo = json_decode((string) $client->get("$base/echo", ['userAgent' => 'one-off/2.0'])->getBody(), true);
echo $echo['headers']['user-agent'], "\n";
$echo = json_decode((string) $client->get("$base/echo")->getBody(), true);
echo $echo['headers']['user-agent'], "\n";Output
inventory-sync/1.0 one-off/2.0 inventory-sync/1.0
HttpClientOptions#
$options = new HttpClientOptions(['timeoutMs' => 3000, 'userAgent' => 'my-app/1.0']);
$client = new HttpClient($options);
$client->get('https://example.com/slow', ['timeoutMs' => 30000]);HttpClientOptions::__construct#
$options = new HttpClientOptions(['followLocation' => false, 'maxRedirs' => 0]);HttpClientOptions::overrideFrom#
$defaults = new HttpClientOptions(['timeoutMs' => 5000, 'headers' => ['Accept: text/html']]);
$one = $defaults->overrideFrom(['timeoutMs' => 100]);
// $one->timeoutMs is 100, $one->headers is still ['Accept: text/html']; $defaults is unchangedNetworkException#
use phasync\HttpClient\HttpClient;
use phasync\HttpClient\NetworkException;
use phasync\Psr\Request;
// A port nothing listens on
$socket = stream_socket_server('tcp://127.0.0.1:0');
$url = 'http://' . stream_socket_get_name($socket, false) . '/';
fclose($socket);
try {
(new HttpClient())->sendRequest(Request::create('GET', $url));
} catch (NetworkException $e) {
echo get_class($e), "\n";
echo 'cURL error ', $e->getCode(), ' (CURLE_COULDNT_CONNECT is ', CURLE_COULDNT_CONNECT, ")\n";
}Output
phasync\HttpClient\NetworkException cURL error 7 (CURLE_COULDNT_CONNECT is 7)
NetworkException#
try {
$response = $client->sendRequest($request);
} catch (\Psr\Http\Client\NetworkExceptionInterface $e) {
// retry later; $e->getRequest() is the request that failed
}RequestException#
use phasync\HttpClient\HttpClient;
use phasync\HttpClient\RequestException;
use phasync\Psr\Request;
$client = new HttpClient();
foreach (['foo://example.com/', '/no-host'] as $url) {
try {
$client->sendRequest(Request::create('GET', $url));
} catch (RequestException $e) {
// the request was invalid: nothing went over the network
echo $e->getMessage(), "\n";
}
}Output
Protocol "foo" not supported or disabled in libcurl URL rejected: No host part in the URL
RequestException#
try {
$client->sendRequest(\phasync\Psr\Request::create('GET', '/no/host'));
} catch (\Psr\Http\Client\RequestExceptionInterface $e) {
echo $e->getMessage();
}