Implemented header packing logic

This commit is contained in:
Chris 2024-02-25 15:49:14 +01:00
parent ff824cdaba
commit b706afaf67
4 changed files with 62 additions and 7 deletions

View File

@ -6,9 +6,9 @@ This is a project that is exploring the feasability and practicality of bringing
Currently implemented: Currently implemented:
* Huffman encoding (for compressed headers) * Huffman encoding and decoding (for compressed headers)
* Header parsing (including dictionary) * Header parsing and packing (only static dictionary, no dynamic dictionary)
* Frame parsing (not all frame types) * Frame parsing core (not all frame types)
## Notes ## Notes

View File

@ -2,15 +2,18 @@
namespace NoccyLabs\React\Http2\Header; namespace NoccyLabs\React\Http2\Header;
use ArrayIterator;
use IteratorAggregate;
use NoccyLabs\React\Http2\Huffman\Codec; use NoccyLabs\React\Http2\Huffman\Codec;
use NoccyLabs\React\Http2\Huffman\Dictionary; use NoccyLabs\React\Http2\Huffman\Dictionary;
use Traversable;
/** /**
* An ordered list of HTTP/2 headers * An ordered list of HTTP/2 headers
* *
* *
*/ */
class HeaderBag class HeaderBag implements IteratorAggregate
{ {
private array $headers = []; private array $headers = [];
@ -24,4 +27,9 @@ class HeaderBag
{ {
$this->headers[] = [ $name, $value ]; $this->headers[] = [ $name, $value ];
} }
public function getIterator(): Traversable
{
return new ArrayIterator($this->headers);
}
} }

View File

@ -79,6 +79,8 @@ class HeaderPacker
]; ];
private const STATIC_TABLE_SIZE = 62; // considering unused index 0 private const STATIC_TABLE_SIZE = 62; // considering unused index 0
private array $dynamicTable = [];
public function __construct() public function __construct()
{ {
if (!self::$huffman) self::$huffman = new Codec(Dictionary::createRfc7541Dictionary()); if (!self::$huffman) self::$huffman = new Codec(Dictionary::createRfc7541Dictionary());
@ -88,6 +90,20 @@ class HeaderPacker
{ {
$packed = ''; $packed = '';
foreach ($headers as [$header,$value]) {
if ($index = $this->lookupIndexForHeader($header,$value)) {
// Indexed
$packed .= chr(0x80 | ($index & 0x7F));
} elseif ($index = $this->lookupIndexForHeader($header,null)) {
// Indexed literal
$packed .= chr(0x40 | ($index & 0x3F));
$value = self::$huffman->encode($value);
$packed .= chr(0x80 | strlen($value) & 0x7F);
$packed .= $value;
} else {
// Literal
}
}
return $packed; return $packed;
} }
@ -149,7 +165,21 @@ class HeaderPacker
private function getIndexedHeader(int $index): array private function getIndexedHeader(int $index): array
{ {
return self::$staticTable[$index]; if ($index < self::STATIC_TABLE_SIZE)
return self::$staticTable[$index];
// TODO bounds check
return $this->dynamicTable[$index - self::STATIC_TABLE_SIZE];
}
private function lookupIndexForHeader(string $header, ?string $value): ?int
{
foreach (self::$staticTable as $index=>[$iName, $iValue]) {
if ($header === $iName && $value === $iValue) return $index;
}
foreach ($this->dynamicTable as $index=>[$iName, $iValue]) {
if ($header === $iName && $value === $iValue) return $index + self::STATIC_TABLE_SIZE;
}
return null;
} }
} }

View File

@ -26,5 +26,22 @@ class HeaderPackerTest extends \PHPUnit\Framework\TestCase
} }
public function testPackingExamplesFromRfc7541()
{
$packer = new HeaderPacker();
$expect = "\x82\x86\x84\x41\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff";
$in = new HeaderBag([
':method' => 'GET',
':scheme' => 'http',
':path' => '/',
':authority' => 'www.example.com'
]);
$out = $packer->packHeaders($in);
$this->assertEquals($expect, $out);
}
} }