phasync
HTTP client / Examples

HTTP client

Examples

Every example of this topic, with its output.

Request#

Example 1
$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#

Example 1
$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#

Example 1
$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#

Example 1
$uri = new Uri('https://example.com/search?q=phasync');
$uri->getHost();                      // "example.com"
(string) $uri->withPath('/docs');     // "https://example.com/docs?q=phasync"

ClientException#

Example 1Too many redirects
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#

Example 1
try {
    $client->sendRequest($request);
} catch (\Psr\Http\Client\ClientExceptionInterface $e) {
    echo $e->getMessage(), ' (cURL error ', $e->getCode(), ')';
}

CurlResponse#

Example 1Read the body as it arrives
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#

Example 1
$response = $client->get('https://example.com/report.csv');

if (200 === $response->getStatusCode()) {
    $body = $response->getBody();
    while (!$body->eof()) {
        echo $body->read(8192);
    }
}

CurlResponse::getStatusCode#

Example 1
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#

Example 1
echo $client->get('https://example.com/')->getHeaderLine('content-type');

CurlResponse::getBody#

Example 1
$json = \json_decode((string) $client->get('https://example.com/data.json')->getBody(), true);

CurlResponse::getDownloadedBytes#

Example 1
$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#

Example 1Two requests that run together
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#

Example 1Requests in coroutines
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#

Example 1
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#

Example 1
$client = new HttpClient([
    'userAgent'  => 'my-app/1.0',
    'timeoutMs'  => 5000,
    'maxRedirs'  => 5,
]);

HttpClient::addMiddlewareFunction#

Example 1A header and a log line for every request
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#

Example 1
$client->addMiddlewareFunction(
    function (RequestInterface $request, ClientInterface $next): ResponseInterface {
        return $next->sendRequest($request->withHeader('X-Request-Id', \bin2hex(\random_bytes(8))));
    }
);

HttpClient::sendRequest#

Example 1A PSR-18 request, and an error status
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#

Example 1
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#

Example 1Send several, read them later
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#

Example 1
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 together

HttpClient::request#

Example 1JSON, headers and a query string
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#

Example 1
$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#

Example 1
$response = $client->get('https://example.com/search?q=phasync', ['timeoutMs' => 2000]);
$data     = \json_decode((string) $response->getBody(), true);

HttpClient::post#

Example 1Post a form
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#

Example 1
$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#

Example 1
$response = $client->put('https://example.com/items/7', \json_encode(['name' => 'x']), [
    'headers' => ['Content-Type: application/json'],
]);

HttpClientOptions#

Example 1Defaults, and options for one request
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#

Example 1
$options = new HttpClientOptions(['timeoutMs' => 3000, 'userAgent' => 'my-app/1.0']);
$client  = new HttpClient($options);

$client->get('https://example.com/slow', ['timeoutMs' => 30000]);

HttpClientOptions::__construct#

Example 1
$options = new HttpClientOptions(['followLocation' => false, 'maxRedirs' => 0]);

HttpClientOptions::overrideFrom#

Example 1
$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 unchanged

NetworkException#

Example 1Connection refused
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#

Example 1
try {
    $response = $client->sendRequest($request);
} catch (\Psr\Http\Client\NetworkExceptionInterface $e) {
    // retry later; $e->getRequest() is the request that failed
}

RequestException#

Example 1Requests that cannot be sent
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#

Example 1
try {
    $client->sendRequest(\phasync\Psr\Request::create('GET', '/no/host'));
} catch (\Psr\Http\Client\RequestExceptionInterface $e) {
    echo $e->getMessage();
}