HttpMachine.PCL
6.0.0
See the version list below for details.
dotnet add package HttpMachine.PCL --version 6.0.0
NuGet\Install-Package HttpMachine.PCL -Version 6.0.0
<PackageReference Include="HttpMachine.PCL" Version="6.0.0" />
<PackageVersion Include="HttpMachine.PCL" Version="6.0.0" />
<PackageReference Include="HttpMachine.PCL" />
paket add HttpMachine.PCL --version 6.0.0
#r "nuget: HttpMachine.PCL, 6.0.0"
#:package HttpMachine.PCL@6.0.0
#addin nuget:?package=HttpMachine.PCL&version=6.0.0
#tool nuget:?package=HttpMachine.PCL&version=6.0.0
HttpMachine
Targets: .NET Standard 2.0 & 2.1, .NET 6, .NET 8 and .NET 10.
Please star this project if you find it useful. Thank you.
Background
This is a fork of the great work done by Benjamin van der Veen. The Original HttpMachine
HttpMachine.PCL is a combined C# HTTP request/response parser. It implements a state machine built with Adrian Thurston's excellent state machine compiler, Ragel.
HttpMachine is Copyright (c) 2011 Benjamin van der Veen. HttpMachine is licensed under the MIT License. See LICENCE.md.
I've forked this project as Benjamin is no longer maintaining the work.
Features
- HTTP/1.1 and 1.0
- Parses requests and responses with one combined parser — filter on
MessageTypeto see which one was parsed - Supports pipelined messages
- Tells your server if it should keep the connection alive (
ShouldKeepAlive) - Extracts the length of the entity body (
Content-Length) - Supports chunked Transfer-Encoding
- Handles zero-length headers (for example
EXT:as used in UPnP) - Header values are collected in an
IEnumerable<string>per header name (repeated headers accumulate)
Usage
using System;
using System.IO;
using System.Linq;
using System.Text;
using HttpMachine;
var data = Encoding.UTF8.GetBytes(
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Length: 14\r\n" +
"\r\n" +
"This is a test");
using var handler = new HttpParserDelegate();
using var parser = new HttpCombinedParser(handler);
if (parser.Execute(data) != data.Length)
{
Console.WriteLine("Parse failed.");
return;
}
var result = handler.HttpRequestResponse;
Console.WriteLine($"Type: {result.MessageType}"); // Response
Console.WriteLine($"Status: {result.StatusCode}"); // 200
Console.WriteLine($"Keep-alive: {result.ShouldKeepAlive}");
foreach (var header in result.Headers)
{
Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
result.Body.Position = 0;
Console.WriteLine($"Body: {new StreamReader(result.Body).ReadToEnd()}");
See the console test project for a complete example including chunked transfer encoding, and the test suite for more usage patterns.
Things worth knowing
- Return value of
Execute: the number of bytes consumed. If it is less than the length you passed in, the parser hit a parse error at that position. - Feeding data incrementally: call
Executerepeatedly as data arrives; messages may be split across buffers at any point. - Span support:
Execute(ReadOnlySpan<byte>)parses without requiring an array — useful with pooled buffers orPipeReaderslices. Body callbacks mirror the input: array-basedExecuteoverloads deliverOnBody(ArraySegment<byte>)over your buffer (as in 5.x); the span overload deliversOnBody(ReadOnlySpan<byte>)to delegates implementingIHttpParserSpanDelegate(whichHttpParserDelegatedoes), or a pooled copy to plainIHttpParserDelegateimplementors. Either way the buffer is only valid during the callback — copy it if you keep it. - End of stream: when a message has no
Content-Lengthand is not chunked, the body runs until the connection closes. Signal this to the parser by callingExecutewith an empty buffer. - Header keys: stored uppercased; the
Headersdictionary uses a case-insensitive comparer, soheaders["content-type"]works too. - Delegate results:
handler.HttpRequestResponseis the snapshot of the most recently completed message. It isnulluntil the first message has been fully parsed. When parsing pipelined messages, read it fromOnMessageEnd(override it) if you need every message. - Customizing: subclass
HttpParserDelegateand override theOn...methods you care about, or implementIHttpParserCombinedDelegatefrom scratch. - The HTTP method accepts these additional four characters:
$ - , . - When both
Content-LengthandTransfer-Encoding: chunkedare present, chunked wins (RFC 9112 6.3).
Working on the parser (Ragel)
src/main/HttpMachine.netstandard/HttpCombinedParser.cs is generated code — do not edit it by hand. The sources are:
- HttpParser2-chunked.cs.rl — C# host file with the parser actions
- http-chunked.rl — the HTTP grammar
- uri.rl — the URI grammar
To regenerate, install Ragel 6.x (apt install ragel on Debian/Ubuntu, available via Homebrew and Cygwin elsewhere) and run generate.sh from the rl directory. The script also widens a few generated sbyte[] tables to byte[] — Ragel 6.x picks the element type by table length, not value range, and some tables contain values above 127.
Run the tests after regenerating:
cd src/tests/HttpMachine.Tests
dotnet test
Version history
6.0
- Breaking: removed the unused
ParserStatusenum. - Added
Execute(ReadOnlySpan<byte>)and the optionalIHttpParserSpanDelegateinterface for zero-copy body delivery;IHttpParserDelegateis unchanged, so existing delegates keep working. - Targets .NET 10 instead of .NET 9 (C# 14); .NET Standard 2.0/2.1, .NET 6 and .NET 8 unchanged (
System.Memorydependency added for .NET Standard 2.0). - Performance:
Execute(MemoryStream)no longer copies the stream when its buffer is exposable; chunk sizes and status codes are parsed without intermediate string allocations. - Fixed
ShouldKeepAlivefor HTTP/1.0 messages (Connection: keep-alive/closewere inverted). - Fixed bodies delimited by connection close (no
Content-Length, not chunked); signal EOF with an emptyExecutecall. - Fixed repeated headers separated by other headers (previously failed the whole parse).
- The same
HttpParserDelegatenow works for pipelined/keep-alive message sequences. Transfer-Encoding: chunkednow takes precedence overContent-Lengthper RFC 9112.- Header dictionary lookups are now case-insensitive.
- Added an xunit test suite and CI; repository cleanup (removed bundled
ragel.exeand stale build scripts).
4.0+
- .NET Standard 2.0 is now the required minimum version.
- Refactoring and simplification of use has caused some namespaces to change.
- It is no longer necessary to implement the interface
IHttpCombinedParser; instead simply new upHttpParserDelegate. Methods can be overridden if needed. - Header values are collected in an
IEnumerable<string>.
3.1.0
- The parser handler must implement
IHttpCombinedParserinstead of usingHttpCombinedParser.
1.1.1
- The parser was combined into one request/response parser. Filter on
MessageTypeto see what type was passed.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- IHttpMachine (>= 6.0.0)
- System.Memory (>= 4.6.3)
-
.NETStandard 2.1
- IHttpMachine (>= 6.0.0)
-
net10.0
- IHttpMachine (>= 6.0.0)
-
net6.0
- IHttpMachine (>= 6.0.0)
-
net8.0
- IHttpMachine (>= 6.0.0)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on HttpMachine.PCL:
| Package | Downloads |
|---|---|
|
WebsocketClientLite.PCL
A simple and light WebSocket client. Can be set to ignore SSL/TLS server certificate issues (use with care!). Easily and effectively observe incoming message using Reactive Extensions (Rx) |
|
|
SimpleHttpListener
Simple Http Listener This is the legacy version of Simple HTTP Listener. Please see project web site for a new version. |
|
|
SimpleHttpListener.Rx
A simple Reactive Extensions (Rx) based HTTP listener for TCP, UDP and multicast, supporting HTTP keep-alive and SSDP-style datagrams. |
|
|
WebsocketClientLite.Rx2
Uses Rx v2.5.5 |
|
|
SwebSocket
An easy to use WebSocket library. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Span-based Execute and zero-copy body delivery via IHttpParserSpanDelegate. HTTP/1.0 keep-alive fix, close-delimited body support, chunked precedence per RFC 9112, pipelining fixes, case-insensitive header lookup. Targets .NET 10. Breaking: removed unused ParserStatus enum. See README version history.