😺
【PHP】ReactPHP の EventLoop で HTTP/2 フレームを送信する
ブロッキングなストリームで HTTP/2 フレームを送信して正常にレスポンスを取得することができた。
> php client.php
00000604000000000000030000006400000004010000000000005c010400000001887690aa69d29ae452a9a74a6b13015db12e0f5889a47e561cc58197000f6197df3dbf4a05e532db4282009a5022b8cbb71b6d4c5a37ff0f0d01366c96dc34fd280714cb6d0a08026940bf702fdc6db53168df5f87497ca589d34d1f00000600010000000148656c6c6f0a
client.php
<?php
use React\EventLoop\Loop;
require __DIR__ . '/vendor/autoload.php';
// https://github.com/reactphp/event-loop/blob/f2fb5a22568c4d29737d69208d1394ac47d77a38/examples/13-http-client-blocking.php
// https://gist.github.com/masakielastic/8840124754b38663373f8f74d6f162d4
require 'h2frames.php';
// TLS なしの h2c サーバー
// https://zenn.dev/masakielastic/articles/618f9f44f327f4
$stream = stream_socket_client('tcp://0.0.0.0:8000');
if (!$stream) {
exit(1);
}
stream_set_blocking($stream, false);
fwrite($stream, h2frames('pri'));
fwrite($stream, h2frames('settings'));
fwrite($stream, h2frames('ack'));
fwrite($stream, h2frames('headers'));
Loop::addReadStream($stream, function ($stream) {
$chunk = fread($stream, 64 * 1024);
echo bin2hex($chunk), PHP_EOL;
Loop::removeReadStream($stream);
fclose($stream);
});
追記。ノンブロッキングなコードでも正常にレスポンスを取得できたので記載する。Loop::addReadStream
のなかでのループの処理は手抜きをしている。本来はレスポンスを DATA (0x00) フレームまで解析しなければならないが、著者の環境では ACK (0x04) を取得した時点ですべてのレスポンスが読み込まれるので、それを確認して終了している。
php client.php
Connecting[connected]
type
04
flag
00
stream
000006040000000000000300000064
type
04
flag
01
stream
00000004010000000000005b010400000001887690aa69d29ae452a9a74a6b13015db12e0f5889a47e561cc58197000f6196df3dbf4a05e532db4282009a502cdc102e09f53168df0f0d01366c96dc34fd280714cb6d0a08026940bf702fdc6db53168df5f87497ca589d34d1f00000600010000000148656c6c6f0a
[END]
client.php
<?php
// https://github.com/reactphp/event-loop/blob/v1.5.0/examples/14-http-client-async.php
use React\EventLoop\Factory;
use React\EventLoop\Loop;
require __DIR__ . '/vendor/autoload.php';
require 'h2frames.php';
$ip = gethostbyname('localhost');
if (ip2long($ip) === false) {
echo 'Unable to resolve hostname' . PHP_EOL;
exit(1);
}
$stream = stream_socket_client('tcp://' . $ip . ':8000', $errno, $errstr, null, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_C>
if (!$stream) {
exit(1);
}
stream_set_blocking($stream, false);
echo 'Connecting';
$timer = Loop::addPeriodicTimer(0.01, function () {
echo '.', PHP_EOL;
});
Loop::addWriteStream($stream, function ($stream) use ($timer) {
Loop::removeWriteStream($stream);
Loop::cancelTimer($timer);
if (stream_socket_get_name($stream, true) === false) {
echo '[unable to connect]' . PHP_EOL;
exit(1);
} else {
echo '[connected]' . PHP_EOL;
}
fwrite($stream, h2frames('pri'));
fwrite($stream, h2frames('settings'));
fwrite($stream, h2frames('ack'));
fwrite($stream, h2frames('headers'));
Loop::addReadStream($stream, function ($stream) {
$chunk = fread($stream, 64 * 1024);
$type = substr($chunk, 3, 1);
$flag = substr($chunk, 4, 1);
if ($chunk === '') {
echo '[END]' . PHP_EOL;
Loop::removeReadStream($stream);
fclose($stream);
return;
}
echo "type", PHP_EOL;
echo bin2hex($type), PHP_EOL;
echo "flag", PHP_EOL;
echo bin2hex($flag), PHP_EOL;
echo "stream", PHP_EOL;
echo bin2hex($chunk), PHP_EOL;
echo PHP_EOL;
if ($type === "\x04" && $flag === "\x01" && strlen($chunk) > 9) {
echo '[END]' . PHP_EOL;
Loop::removeReadStream($stream);
fclose($stream);
return;
}
});
});
Discussion