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\EventEmitterInterface;
|
||||||
use Evenement\EventEmitterTrait;
|
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 NoccyLabs\React\Http2\Stream\Http2Stream;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use React\Stream\DuplexStreamInterface;
|
use React\Stream\DuplexStreamInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,13 +39,54 @@ class Http2Connection implements EventEmitterInterface
|
|||||||
/** @var DuplexStreamInterface The connection to the client */
|
/** @var DuplexStreamInterface The connection to the client */
|
||||||
private DuplexStreamInterface $connection;
|
private DuplexStreamInterface $connection;
|
||||||
|
|
||||||
public function __construct(DuplexStreamInterface $connection)
|
private string $buffer = '';
|
||||||
|
|
||||||
|
public function __construct(DuplexStreamInterface $connection, ServerRequestInterface $request, ?SettingsFrame $http2settings)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$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.
|
* When a new stream is opened, clean up older streams that are idle or closed.
|
||||||
*
|
*
|
||||||
|
@ -3,8 +3,15 @@
|
|||||||
namespace NoccyLabs\React\Http2;
|
namespace NoccyLabs\React\Http2;
|
||||||
|
|
||||||
use NoccyLabs\React\Http2\Connection\Http2Connection;
|
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\Frame\SettingsFrame;
|
||||||
|
use NoccyLabs\React\Http2\Header\HeaderPacker;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
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\DuplexResourceStream;
|
||||||
use React\Stream\DuplexStreamInterface;
|
use React\Stream\DuplexStreamInterface;
|
||||||
use React\Stream\ThroughStream;
|
use React\Stream\ThroughStream;
|
||||||
@ -24,11 +31,46 @@ class Http2Middleware
|
|||||||
{
|
{
|
||||||
// expect upgrade h2 for secure connections, h2c for plaintext
|
// expect upgrade h2 for secure connections, h2c for plaintext
|
||||||
|
|
||||||
// TODO handle HTTP/2 upgrade from HTTP/1.1
|
$requestSecure = $request->getHeaderLine("x-forwarded-proto") ?: "http";
|
||||||
// TODO handle HTTP/2 with prior knowledge
|
$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.
|
* Parse the settings frame present in the HTTP/1.1 upgrade request.
|
||||||
*
|
*
|
||||||
@ -61,4 +103,4 @@ class Http2Middleware
|
|||||||
|
|
||||||
return $connection;
|
return $connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use NoccyLabs\React\Http2\Connection\Http2Connection;
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
// TODO add DuplexStreamInterface here
|
||||||
class Http2Stream
|
class Http2Stream
|
||||||
{
|
{
|
||||||
const STATE_IDLE = 0;
|
const STATE_IDLE = 0;
|
||||||
@ -22,6 +23,13 @@ class Http2Stream
|
|||||||
|
|
||||||
private int $state = self::STATE_IDLE;
|
private int $state = self::STATE_IDLE;
|
||||||
|
|
||||||
|
private int $index;
|
||||||
|
|
||||||
private Http2Connection $connection;
|
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