Glyph11.Pico
The fastest path to a BinaryRequest.
picohttpparser (native, SSE4.2) tokenizes; a thin
managed layer fills the same BinaryRequest as Glyph11. The
trade: no validation beyond picohttpparser's — raw speed, same request shape.
Install
dotnet add package Glyph11.Pico
Depends on Glyph11 (for BinaryRequest and chunked decoding) and bundles the
native libglyph11pico per platform.
Quick start
using System.Text;
using Glyph11.Pico;
using Glyph11.Protocol; // BinaryRequest
// Same reusable BinaryRequest as the managed parser — allocate once, Clear() per request.
var request = new BinaryRequest();
// Contiguous input (a byte[] / ReadOnlyMemory<byte> you already have buffered).
byte[] input = "GET /api/users?page=1 HTTP/1.1\r\nHost: example.com\r\n\r\n"u8.ToArray();
// Returns true on a parsed header block; false if malformed or incomplete.
// `consumed` marks the header block (see "The trade-off" for the off-by-one note).
if (PicoParser.TryParse(input, request, out int consumed))
{
// Identical BinaryRequest to Glyph11 — Method / Path / Version / Headers / QueryParameters.
Console.WriteLine(Encoding.ASCII.GetString(request.Method.Span)); // GET
Console.WriteLine(Encoding.ASCII.GetString(request.Path.Span)); // /api/users
}
Reading the result
Because it fills the same BinaryRequest, reading is identical to
Glyph11 — every field is a zero-copy
ReadOnlyMemory<byte> slice of your input.
request.Method; // "GET"
request.Path; // "/api/users" (query stripped)
request.Version; // "HTTP/1.1"
request.Body; // remainder of the buffer after the header block
for (int i = 0; i < request.Headers.Count; i++)
{
var (name, value) = request.Headers[i]; // KeyValuePair of byte slices
// ...
}
for (int i = 0; i < request.QueryParameters.Count; i++)
{
var (key, val) = request.QueryParameters[i];
// ...
}
The header capacity per parse is PicoParser.MaxHeaders (256); a request with more headers
is rejected (false).
ReadOnlySequence input
picohttpparser needs one contiguous buffer, so a fragmented sequence uses a dedicated overload:
single-segment is parsed in place (zero-copy); multi-segment is linearized into a fresh array, which the
resulting BinaryRequest slices keep alive (just like the managed parser — no buffer for you
to manage).
// `sequence` is a ReadOnlySequence<byte> from a PipeReader, socket, etc.
if (PicoParser.TryParse(sequence, request, out int consumed))
{
// request.* exactly as in the contiguous case.
}
// Single-segment → zero-copy. Multi-segment → one ToArray() internally; the BinaryRequest
// references that array, so it stays alive as long as you hold the request.
Chunked bodies
Chunked decoding is glyph11's — this package depends on Glyph11, so use
ChunkedBodyStream directly (see the Glyph11 page).
using Glyph11.Parser;
var decoder = new ChunkedBodyStream();
var r = decoder.TryReadChunk(input, out int consumed, out int dataOffset, out int dataLength);
// r: Chunk | Completed | NeedMoreData | Error
The trade-off
Glyph11.Pico does the same parsing as Glyph11, but only picohttpparser's validation. It does not do glyph11's hardening:
| Glyph11 / Glyph11.Native | Glyph11.Pico | |
|---|---|---|
| Method / path / version / headers / query | ✅ | ✅ (same BinaryRequest) |
| Token / field-value character validation | ✅ | ❌ |
| Path normalization & traversal checks | ✅ | ❌ |
| Host / limits / Transfer-Encoding+Content-Length conflict | ✅ | ❌ |
| Request-smuggling defenses | ✅ | ❌ |
consumed follows glyph11's convention: it's the header-block length minus one, so the body
begins at consumed + 1 — a drop-in match for the managed parser.
Platforms & native resolution
Native libglyph11pico is bundled for linux-x64/arm64,
win-x64/arm64, and osx-x64/arm64, resolved automatically. The x86-64 builds use
picohttpparser's SSE4.2 fast path. Override with GLYPH11_PICO_NATIVE_PATH (an explicit path
to libglyph11pico.{so,dll,dylib}) for local builds.