Implemented header packing logic
This commit is contained in:
parent
ff824cdaba
commit
b706afaf67
@ -6,9 +6,9 @@ This is a project that is exploring the feasability and practicality of bringing
|
||||
|
||||
Currently implemented:
|
||||
|
||||
* Huffman encoding (for compressed headers)
|
||||
* Header parsing (including dictionary)
|
||||
* Frame parsing (not all frame types)
|
||||
* Huffman encoding and decoding (for compressed headers)
|
||||
* Header parsing and packing (only static dictionary, no dynamic dictionary)
|
||||
* Frame parsing core (not all frame types)
|
||||
|
||||
## Notes
|
||||
|
||||
|
@ -2,15 +2,18 @@
|
||||
|
||||
namespace NoccyLabs\React\Http2\Header;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use NoccyLabs\React\Http2\Huffman\Codec;
|
||||
use NoccyLabs\React\Http2\Huffman\Dictionary;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* An ordered list of HTTP/2 headers
|
||||
*
|
||||
*
|
||||
*/
|
||||
class HeaderBag
|
||||
class HeaderBag implements IteratorAggregate
|
||||
{
|
||||
|
||||
private array $headers = [];
|
||||
@ -24,4 +27,9 @@ class HeaderBag
|
||||
{
|
||||
$this->headers[] = [ $name, $value ];
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->headers);
|
||||
}
|
||||
}
|
@ -79,6 +79,8 @@ class HeaderPacker
|
||||
];
|
||||
private const STATIC_TABLE_SIZE = 62; // considering unused index 0
|
||||
|
||||
private array $dynamicTable = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!self::$huffman) self::$huffman = new Codec(Dictionary::createRfc7541Dictionary());
|
||||
@ -88,6 +90,20 @@ class HeaderPacker
|
||||
{
|
||||
$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;
|
||||
}
|
||||
@ -149,7 +165,21 @@ class HeaderPacker
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user