Added tests for Http2Middleware
* Added unit tests for middleware * Message handling code
This commit is contained in:
parent
d41cfb1be2
commit
dc3225538f
@ -4,7 +4,12 @@ namespace NoccyLabs\React\Http2\Connection;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
use Evenement\EventEmitterTrait;
|
||||
use NoccyLabs\React\Http2\Frame\DataFrame;
|
||||
use NoccyLabs\React\Http2\Frame\Frame;
|
||||
use NoccyLabs\React\Http2\Frame\HeadersFrame;
|
||||
use NoccyLabs\React\Http2\Frame\SettingsFrame;
|
||||
use NoccyLabs\React\Http2\Stream\Http2Stream;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
@ -34,13 +39,54 @@ class Http2Connection implements EventEmitterInterface
|
||||
/** @var DuplexStreamInterface The connection to the client */
|
||||
private DuplexStreamInterface $connection;
|
||||
|
||||
public function __construct(DuplexStreamInterface $connection)
|
||||
private string $buffer = '';
|
||||
|
||||
public function __construct(DuplexStreamInterface $connection, ServerRequestInterface $request, ?SettingsFrame $http2settings)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
|
||||
|
||||
$connection->on('data', function ($data) {
|
||||
$this->buffer .= $data;
|
||||
$frame = Frame::parseFrame($this->buffer);
|
||||
$this->handleHttp2Frame($frame);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function handleHttp2Frame(Frame $frame)
|
||||
{
|
||||
switch ($frame->getFrameType()) {
|
||||
case Frame::FRAME_HEADERS:
|
||||
$this->handleHttp2HeadersFrame($frame);
|
||||
break;
|
||||
case Frame::FRAME_DATA:
|
||||
$this->handleHttp2DataFrame($frame);
|
||||
break;
|
||||
case Frame::FRAME_PING:
|
||||
$this->handleHttp2PingFrame($frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleHttp2HeadersFrame(HeadersFrame $frame)
|
||||
{
|
||||
$method = $frame->headers->getHeaderLine(':method');
|
||||
$scheme = $frame->headers->getHeaderLine(':scheme');
|
||||
$path = $frame->headers->getHeaderLine(':path');
|
||||
}
|
||||
|
||||
private function handleHttp2DataFrame(DataFrame $frame)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function handleHttp2PingFrame(DataFrame $frame)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* When a new stream is opened, clean up older streams that are idle or closed.
|
||||
*
|
||||
|
@ -3,8 +3,15 @@
|
||||
namespace NoccyLabs\React\Http2;
|
||||
|
||||
use NoccyLabs\React\Http2\Connection\Http2Connection;
|
||||
use NoccyLabs\React\Http2\Frame\DataFrame;
|
||||
use NoccyLabs\React\Http2\Frame\Frame;
|
||||
use NoccyLabs\React\Http2\Frame\HeadersFrame;
|
||||
use NoccyLabs\React\Http2\Frame\SettingsFrame;
|
||||
use NoccyLabs\React\Http2\Header\HeaderPacker;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Stream\CompositeStream;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
use React\Stream\ThroughStream;
|
||||
@ -24,11 +31,46 @@ class Http2Middleware
|
||||
{
|
||||
// expect upgrade h2 for secure connections, h2c for plaintext
|
||||
|
||||
// TODO handle HTTP/2 upgrade from HTTP/1.1
|
||||
// TODO handle HTTP/2 with prior knowledge
|
||||
$requestSecure = $request->getHeaderLine("x-forwarded-proto") ?: "http";
|
||||
$requestUpgrade = $request->getHeaderLine("upgrade");
|
||||
$connectionFlags = array_map(
|
||||
fn($v) => strtolower(trim($v)),
|
||||
explode(",", $request->getHeaderLine("connection"))
|
||||
);
|
||||
|
||||
// Pass everything we aren't interested in on to the next handler
|
||||
if (!in_array('upgrade', $connectionFlags) || !in_array($requestUpgrade, ['h2', 'h2c'])) {
|
||||
if (is_callable($next))
|
||||
return $next($request);
|
||||
return Response::plaintext("Unsupported protocol")->withStatus(Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
// handle HTTP/2 upgrade from HTTP/1.1
|
||||
$http2SettingsData = $request->getHeaderLine("http2-settings");
|
||||
if ($http2SettingsData) {
|
||||
$http2Settings = $this->parseSettingsFromBase64String($http2SettingsData);
|
||||
} else {
|
||||
// TODO handle HTTP/2 with prior knowledge
|
||||
return Response::plaintext("Expected HTTP2-Settings header")->withStatus(Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$responseInputStream = new ThroughStream();
|
||||
$responseOutputStream = new ThroughStream();
|
||||
$responseStream = new CompositeStream($responseOutputStream, $responseInputStream);
|
||||
$serverStream = new CompositeStream($responseInputStream, $responseOutputStream);
|
||||
|
||||
$connection = new Http2Connection($serverStream, $request, $http2Settings??null);
|
||||
|
||||
$headers = [
|
||||
'Connection' => 'Upgrade',
|
||||
'Upgrade' => $requestUpgrade
|
||||
];
|
||||
|
||||
return (new Response(Response::STATUS_SWITCHING_PROTOCOLS, $headers, $responseStream));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the settings frame present in the HTTP/1.1 upgrade request.
|
||||
*
|
||||
@ -61,4 +103,4 @@ class Http2Middleware
|
||||
|
||||
return $connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use NoccyLabs\React\Http2\Connection\Http2Connection;
|
||||
*
|
||||
*
|
||||
*/
|
||||
// TODO add DuplexStreamInterface here
|
||||
class Http2Stream
|
||||
{
|
||||
const STATE_IDLE = 0;
|
||||
@ -22,6 +23,13 @@ class Http2Stream
|
||||
|
||||
private int $state = self::STATE_IDLE;
|
||||
|
||||
private int $index;
|
||||
|
||||
private Http2Connection $connection;
|
||||
|
||||
}
|
||||
public function __construct(Http2Connection $connection, int $index)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
50
tests/Http2MiddlewareTest.php
Normal file
50
tests/Http2MiddlewareTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\Http2;
|
||||
|
||||
use NoccyLabs\React\Http2\Frame\SettingsFrame;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
|
||||
#[CoversClass(Http2Middleware::class)]
|
||||
class Http2MiddlewareTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
|
||||
public function testInvalidUpgradeRequests()
|
||||
{
|
||||
$request = new ServerRequest("GET", "/", [
|
||||
"Upgrade" => "h2",
|
||||
"x-forwarded-proto" => "http"
|
||||
]);
|
||||
|
||||
$middleware = new Http2Middleware();
|
||||
|
||||
/** @var ResponseInterface $response */
|
||||
$response = $middleware($request);
|
||||
|
||||
$this->assertEquals(Response::STATUS_BAD_REQUEST, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testHandlingUpgradeRequest()
|
||||
{
|
||||
$http2settings = new SettingsFrame();
|
||||
$http2settings->set(SettingsFrame::SETTINGS_MAX_CONCURRENT_STREAMS, 64);
|
||||
|
||||
$request = new ServerRequest("GET", "/", [
|
||||
"Upgrade" => "h2c",
|
||||
"Connection" => "upgrade",
|
||||
"x-forwarded-proto" => "http",
|
||||
"HTTP2-Settings" => $http2settings->toBinary(),
|
||||
]);
|
||||
|
||||
$middleware = new Http2Middleware();
|
||||
|
||||
/** @var ResponseInterface $response */
|
||||
$response = $middleware($request);
|
||||
|
||||
$this->assertEquals(Response::STATUS_SWITCHING_PROTOCOLS, $response->getStatusCode());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user