Skip to content

Commit

Permalink
Add tests for Http3Driver
Browse files Browse the repository at this point in the history
upgrading() tests still pending
  • Loading branch information
bwoebi committed Feb 11, 2024
1 parent 12aecf8 commit e0e2bc1
Show file tree
Hide file tree
Showing 8 changed files with 609 additions and 130 deletions.
9 changes: 5 additions & 4 deletions src/Driver/Http2Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,6 @@ public function handleHeaders(int $streamId, array $pseudo, array $headers, bool
Http2Parser::PROTOCOL_ERROR
);
}
// Stuff it into the headers for applications to read
$headers[":protocol"] = [$value];
} else {
throw new Http2StreamException(
"Invalid pseudo header",
Expand Down Expand Up @@ -1063,7 +1061,9 @@ public function handleHeaders(int $streamId, array $pseudo, array $headers, bool
$uri,
$headers,
"",
"2"
"2",
null,
$pseudo[":protocol"] ?? "",
);

$this->streamIdMap[\spl_object_hash($request)] = $streamId;
Expand Down Expand Up @@ -1139,7 +1139,8 @@ function (int $bodySize) use ($streamId) {
$headers,
$body,
"2",
$trailers
$trailers,
$pseudo[":protocol"] ?? "",
);

$this->streamIdMap[\spl_object_hash($request)] = $streamId;
Expand Down
173 changes: 96 additions & 77 deletions src/Driver/Http3Driver.php

Large diffs are not rendered by default.

24 changes: 13 additions & 11 deletions src/Driver/Internal/Http3/Http3Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,11 @@ public function awaitHttpResponse(QuicSocket $stream): \Generator
return $this->readHttpMessage($stream, $buf, $off);
}

private function parsePushPromise(string $contents): array
private function parsePushPromise(QuicSocket $stream, string $contents): array
{
$pushOff = 0;
$pushId = self::decodeVarint($contents, $pushOff);
return [$pushId, self::processHeaders($this->qpack->decode($contents, $pushOff))];
return [$pushId, self::processHeaders($stream, $this->qpack->decode($contents, $pushOff))];
}

private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \Generator
Expand All @@ -182,7 +182,7 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
}
if ($frame === Http3Frame::PUSH_PROMISE) {
/** @psalm-suppress PossiblyNullArgument https://github.com/vimeo/psalm/issues/10668 */
yield Http3Frame::PUSH_PROMISE => $this->parsePushPromise($contents);
yield Http3Frame::PUSH_PROMISE => $this->parsePushPromise($stream, $contents);
} else {
break;
}
Expand All @@ -192,7 +192,7 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
}
$headerOff = 0;
/** @psalm-suppress PossiblyNullArgument https://github.com/vimeo/psalm/issues/10668 */
yield Http3Frame::HEADERS => self::processHeaders($this->qpack->decode($contents, $headerOff));
yield Http3Frame::HEADERS => self::processHeaders($stream, $this->qpack->decode($contents, $headerOff));
$hadData = false;
while (null !== $type = self::decodeFrameTypeFromStream($stream, $buf, $off)) {
switch ($type) {
Expand All @@ -202,7 +202,7 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
return;
}
$headerOff = 0;
yield Http3Frame::HEADERS => self::processHeaders($this->qpack->decode($headers, $headerOff));
yield Http3Frame::HEADERS => self::processHeaders($stream, $this->qpack->decode($headers, $headerOff));
if ($hadData) {
break 2;
}
Expand Down Expand Up @@ -248,7 +248,7 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
if (!$headers = self::readFrameWithoutType($stream, $buf, $off, $this->headerSizeLimit)) {
return;
}
yield Http3Frame::PUSH_PROMISE => $this->parsePushPromise($headers);
yield Http3Frame::PUSH_PROMISE => $this->parsePushPromise($stream, $headers);
break;

default:
Expand All @@ -261,7 +261,7 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
while ([$frame, $contents] = self::readFullFrame($stream, $buf, $off, $this->headerSizeLimit)) {
if ($frame === Http3Frame::PUSH_PROMISE) {
/** @psalm-suppress PossiblyNullArgument https://github.com/vimeo/psalm/issues/10668 */
yield Http3Frame::PUSH_PROMISE => $this->parsePushPromise($contents);
yield Http3Frame::PUSH_PROMISE => $this->parsePushPromise($stream, $contents);
} else {
/** @psalm-suppress PossiblyNullPropertyFetch https://github.com/vimeo/psalm/issues/10668 */
throw new Http3ConnectionException("Expecting only push promises after a message frame, found {$frame->name}", Http3Error::H3_FRAME_UNEXPECTED);
Expand All @@ -270,23 +270,23 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
}

// TODO extracted from Http2Parser::parseHeaderBuffer. Same rules, make common method?
private static function processHeaders(array $decoded): ?array
private static function processHeaders(QuicSocket $stream, array $decoded): ?array
{
$headers = [];
$pseudo = [];

foreach ($decoded as [$name, $value]) {
if (!\preg_match('/^[\x21-\x40\x5b-\x7e]+$/'/* Http2Parser::HEADER_NAME_REGEX */, $name)) {
return null;
throw new Http3StreamException("Invalid header name format", $stream, Http3Error::H3_MESSAGE_ERROR);
}

if ($name[0] === ':') {
if (!empty($headers)) {
return null;
throw new Http3StreamException("Found pseudo-headers after other headers", $stream, Http3Error::H3_MESSAGE_ERROR);
}

if (isset($pseudo[$name])) {
return null;
throw new Http3StreamException("Found duplicate pseudo-header", $stream, Http3Error::H3_MESSAGE_ERROR);
}

$pseudo[$name] = $value;
Expand Down Expand Up @@ -412,6 +412,8 @@ public function process(): ConcurrentIterator
}
} catch (Http3ConnectionException $e) {
$this->abort($e);
} catch (Http3StreamException $e) {
$e->getStream()->close($e->getCode());
}
});
}
Expand Down
15 changes: 12 additions & 3 deletions src/Driver/Internal/Http3/Http3Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
use Amp\Quic\QuicConnection;
use Amp\Quic\QuicSocket;

/**
* @psalm-import-type HeaderArray from \Amp\Http\Server\Driver\Internal\Http3\QPack
*/
class Http3Writer
{
private QuicSocket $controlStream;

public function __construct(private QuicConnection $connection, private array $settings)
public function __construct(private QuicConnection $connection, private array $settings, private QPack $qpack)
{
$this->startControlStream();
}
Expand Down Expand Up @@ -38,9 +41,15 @@ private static function sendKnownFrame(QuicSocket $stream, Http3Frame $type, str
self::sendFrame($stream, $type->value, $payload);
}

public function sendHeaderFrame(QuicSocket $stream, string $payload): void
/** @param HeaderArray $headers */
public function sendHeaderFrame(QuicSocket $stream, array $headers): void
{
self::sendKnownFrame($stream, Http3Frame::HEADERS, $payload);
self::sendKnownFrame($stream, Http3Frame::HEADERS, $this->qpack->encode($headers));
}

public function sendPushPromiseFrame(QuicSocket $stream, int $pushId, array $headers): void
{
self::sendKnownFrame($stream, Http3Frame::PUSH_PROMISE, self::encodeVarint($pushId) . $this->qpack->encode($headers));
}

public function sendData(QuicSocket $stream, string $payload): void
Expand Down
8 changes: 5 additions & 3 deletions src/Driver/Internal/Http3/QPack.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

/**
* @internal
* @psalm-import-type HeaderArray from \Amp\Http\HPack
* @psalm-type HeaderArray = array<string, list<string>>
*/
class QPack
{
Expand Down Expand Up @@ -255,8 +255,10 @@ public function encode(array $headers): string
{
// @TODO implementation is deliberately primitive... [doesn't use any dynamic table...]
$encodedHeaders = [];
foreach ($headers as [$name, $value]) {
$encodedHeaders[] = ("\x20" | self::encodeDynamicField(0x07, $name)) . self::encodeDynamicField(0x7F, $value);
foreach ($headers as $name => $values) {
foreach ($values as $value) {
$encodedHeaders[] = ("\x20" | self::encodeDynamicField(0x07, $name)) . self::encodeDynamicField(0x7F, $value);
}
}

return "\0\0" . \implode($encodedHeaders);
Expand Down
26 changes: 22 additions & 4 deletions src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class Request extends HttpRequest
* @param non-empty-string $method HTTP request method.
* @param PsrUri $uri The full URI being requested, including host, port, and protocol.
* @param array<non-empty-string, string|string[]> $headers An array of strings or an array of string arrays.
* @param string $protocol HTTP protocol version (e.g. 1.0, 1.1, or 2.0).
* @param string $protocolVersion HTTP protocol version (e.g. 1.0, 1.1, or 2.0).
* @param Trailers|null $trailers Trailers if request has trailers, or null otherwise.
*/
public function __construct(
Expand All @@ -39,8 +39,9 @@ public function __construct(
PsrUri $uri,
array $headers = [],
ReadableStream|string $body = '',
private string $protocol = "1.1",
?Trailers $trailers = null
private string $protocolVersion = "1.1",
?Trailers $trailers = null,
private string $protocol = "",
) {
parent::__construct($method, $uri);

Expand Down Expand Up @@ -86,14 +87,31 @@ public function setUri(PsrUri $uri): void
* it has nothing to do with URI schemes like http:// or https:// ...
*/
public function getProtocolVersion(): string
{
return $this->protocolVersion;
}

/**
* Sets a new protocol version number for the request.
*/
public function setProtocolVersion(string $protocolVersion): void
{
$this->protocolVersion = $protocolVersion;
}

/**
* This method returns the :protocol pseudo-header;
* it has nothing to do with versions nor URI schemes.
*/
public function getProtocol(): string
{
return $this->protocol;
}

/**
* Sets a new protocol version number for the request.
*/
public function setProtocolVersion(string $protocol): void
public function setProtocol(string $protocol): void
{
$this->protocol = $protocol;
}
Expand Down
Loading

0 comments on commit e0e2bc1

Please sign in to comment.