A zero-allocation, hardened HTTP/1.1 request parser for .NET — RFC 9110/9112 validation, configurable resource limits, and request-smuggling / semantic checks fused into a single zero-copy pass. Available as three NuGet packages that trade portability, native speed, and raw throughput.
| Package | What it is | Output | Validation | Dependencies | Best for |
|---|---|---|---|---|---|
| Glyph11 | pure-managed hardened parser | BinaryRequest |
full (RFC + smuggling) | none — runs anywhere | portability, zero deps |
| Glyph11.Native | the C core via P/Invoke | raw field spans | full (same as managed) | native libglyph11 per RID |
native speed, zero-alloc |
| Glyph11.Pico | picohttpparser + managed glue | BinaryRequest |
picohttpparser only | native libglyph11pico + Glyph11 |
maximum speed, validate elsewhere |
All three produce the same data; Glyph11 and Glyph11.Pico fill the identical
BinaryRequest, so swapping between them is a one-line change.
Dependency-free, runs on any .NET target. Parses a ReadOnlySequence<byte> (from a
PipeReader, socket, …) or a contiguous ReadOnlyMemory<byte>.
using System.Buffers;
using System.Text;
using Glyph11.Protocol;
using Glyph11.Parser;
using Glyph11.Parser.UltraHardened;
var request = new BinaryRequest();
var limits = ParserLimits.Default;
ReadOnlySequence<byte> buffer = new(Encoding.ASCII.GetBytes(
"GET /api/users?page=1 HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"));
if (UltraHardenedParser.TryExtractFullHeaderValidated(ref buffer, request, in limits, out int bytesRead))
{
Console.WriteLine(Encoding.ASCII.GetString(request.Method.Span)); // GET
Console.WriteLine(Encoding.ASCII.GetString(request.Path.Span)); // /api/users
for (int i = 0; i < request.Headers.Count; i++)
{
var (name, value) = request.Headers[i]; // zero-copy slices
Console.WriteLine($"{Encoding.ASCII.GetString(name.Span)}: {Encoding.ASCII.GetString(value.Span)}");
}
// advance your reader by bytesRead; reuse `request` across calls (request.Clear()).
}
// throws HttpParseException on a protocol/semantic violation; returns false if incomplete.TryExtractFullHeaderROM(ref ReadOnlyMemory<byte>, …) is the single-buffer fast path.
Same validation, native speed, zero allocation (you provide the field storage). Bundles
libglyph11 for linux-x64/arm64, win-x64/arm64, osx-x64/arm64.
using System.Text;
using Glyph11.Native;
byte[] request = Encoding.ASCII.GetBytes(
"GET /api/users?page=1 HTTP/1.1\r\nHost: example.com\r\n\r\n");
var limits = Glyph11Limits.Default;
Span<Glyph11Field> headers = stackalloc Glyph11Field[(int)limits.MaxHeaderCount];
Span<Glyph11Field> query = stackalloc Glyph11Field[(int)limits.MaxQueryParameterCount];
int status = Glyph11Parser.Parse(request, headers, query, limits, out var r);
if (status == Glyph11Parser.Ok)
{
string Slice(Glyph11Span s) => Encoding.ASCII.GetString(request, (int)s.Offset, (int)s.Length);
Console.WriteLine(Slice(r.Method)); // GET
Console.WriteLine(Slice(r.Path)); // /api/users
for (int i = 0; i < r.HeaderCount; i++)
Console.WriteLine($"{Slice(headers[i].Name)}: {Slice(headers[i].Value)}");
}
// status: 0 = OK, 1 = incomplete, otherwise a protocol/limit error (→ HTTP 400 / 431).A ReadOnlySequence<byte> overload handles fragmented input — single-segment is parsed in
place (zero-copy), multi-segment is linearized into a caller-provided scratch (the C core
needs one contiguous slab). parsed is the contiguous span the offsets index into:
Span<byte> scratch = stackalloc byte[8192]; // sized to your max header block
int status = Glyph11Parser.Parse(sequence, scratch, headers, query, limits,
out var r, out ReadOnlySpan<byte> parsed);
// slice fields against `parsed` — the input's first segment, or `scratch`.
linux-x64requires AVX2 (Haswell / 2013+ — universal on modern servers): the SIMD scanners inline into the parse loop for ~15% on large headers. Other RIDs use the portable baseline.
picohttpparser (native, SSE4.2) tokenizes; minimal
managed glue fills the same BinaryRequest as Glyph11. No validation beyond
picohttpparser's — the trade is raw speed.
using System.Text;
using Glyph11.Pico;
using Glyph11.Protocol;
var request = new BinaryRequest();
byte[] input = "GET /api/users?page=1 HTTP/1.1\r\nHost: example.com\r\n\r\n"u8.ToArray();
if (PicoParser.TryParse(input, request, out int consumed))
{
Console.WriteLine(Encoding.ASCII.GetString(request.Method.Span)); // GET
Console.WriteLine(Encoding.ASCII.GetString(request.Path.Span)); // /api/users
// request.Version / .Headers / .QueryParameters — same shape as Glyph11.
}A ReadOnlySequence<byte> overload is also available — single-segment zero-copy,
multi-segment linearized into a fresh array (the BinaryRequest slices keep it alive):
if (PicoParser.TryParse(sequence, request, out int consumed)) { /* request.* as usual */ }Use it when you want the fastest path to a BinaryRequest and validate elsewhere (or trust
the source). For the hardened parser, use Glyph11.
Both Glyph11 and Glyph11.Pico (which depends on Glyph11) decode chunked
transfer-encoding via ChunkedBodyStream; Glyph11.Native exposes the C decoder.
using Glyph11.Parser;
var stream = new ChunkedBodyStream();
var r = stream.TryReadChunk(input, out int consumed, out int dataOffset, out int dataLength);
// r: Chunk | Completed | NeedMoreData | Errorlinux-x64, best-of-5, ns/op (lower is better). Glyph11.Native / Glyph11.Pico are the
AVX2 / SSE4.2 native builds. The same tables and usage docs are at
https://dotnet-web-stack.github.io/Glyph11/; the harnesses are in
bench/.
Request header parsing — contiguous buffer (→ BinaryRequest for Glyph11 / Pico; raw spans for Native):
| Payload | Glyph11 | Glyph11.Native | Glyph11.Pico |
|---|---|---|---|
| ~95 B | 120 ns | 99 ns | 80 ns |
| 4 KB | 750 ns | 502 ns | 487 ns |
| 32 KB | 5251 ns | 3752 ns | 3370 ns |
Multi-segment — fragmented request (3 segments) linearized into a buffer per request, then parsed:
| Payload | Glyph11 | Glyph11.Native | Glyph11.Pico |
|---|---|---|---|
| ~95 B | 245 ns | 116 ns | 118 ns |
| 4 KB | 1258 ns | 721 ns | 674 ns |
| 32 KB | 8695 ns | 4969 ns | 4627 ns |
Chunked body decoding (decoded size; Glyph11.Pico reuses Glyph11's ChunkedBodyStream, so they share a column):
| Decoded | Glyph11 / Pico | Glyph11.Native |
|---|---|---|
| 256 B | 19 ns | 21 ns |
| 4 KB | 115 ns | 70 ns |
| 32 KB | 826 ns | 808 ns |
Glyph11.Pico is the fastest to a full BinaryRequest — it skips glyph11's hardening, so
pick it only when you validate elsewhere.
The C core is also reachable from the JVM via Panama FFM (JDK 21+) — see
bindings/kotlin. Not a NuGet package.
cmake -S core -B core/build-rel -DGLYPH11_BUILD_TESTS=OFF # libglyph11
cmake --build core/build-rel
cmake -S bindings/dotnet/Glyph11.Pico/native -B pico-build # libglyph11pico
cmake --build pico-buildMIT licensed. Glyph11.Pico bundles picohttpparser (MIT/Perl, © Kazuho Oku et al.).