High-performance HTTP client for .NET — built on Akka.Streams with automatic retries, caching, cookies, HTTP/2 multiplexing, and HTTP/3 (QUIC).
TurboHTTP replaces HttpClient with a reactive, backpressure-aware HTTP pipeline built on Akka.Streams. Actors manage connection lifecycle while data flows through System.Threading.Channels — zero bytes ever touch an actor mailbox. The result: high throughput, low allocations, and a pipeline that never dies on transient errors.
- HTTP/1.0 and HTTP/1.1 — chunked transfer encoding, keep-alive, pipelining
- HTTP/2 — binary framing, stream multiplexing, HPACK header compression, flow control
- HTTP/3 (QUIC) — UDP-based transport, QPACK header compression, 0-RTT connection establishment
- Immortal pipeline — transport failures, protocol violations, and corrupt data are absorbed gracefully. The stream only completes when you dispose the client. No single bad request or broken connection can take down the pipeline.
- Automatic retries — idempotent methods (GET, PUT, DELETE, HEAD, OPTIONS) are retried automatically on transient failures. Respects
Retry-Afterheaders. POST and other non-idempotent methods are never retried. - Connection pooling — per-host connection pools with configurable limits, idle eviction, and automatic reconnect with exponential backoff. Connections are reused transparently across requests.
- Redirect following — 301, 302, 303, 307, 308 with correct method rewriting (POST to GET on 303), body preservation on 307/308, loop detection, and HTTPS-to-HTTP downgrade protection. Configurable max redirects.
- Cookie management — automatic cookie storage and injection across requests. Supports domain/path matching,
Secure,HttpOnly,SameSite,Max-Age, andExpires. Bring your ownCookieJaror use the built-in one. - HTTP caching — in-memory LRU cache with
Varysupport, conditional requests viaETag/If-None-MatchandLast-Modified/If-Modified-Since, freshness evaluation (max-age,s-maxage,Expires, heuristic), and 304 response merging. Configurable viaCachePolicy. - Content encoding — automatic gzip, deflate, and Brotli response decompression. Optional request body compression. Can be disabled per-client if you need raw compressed bytes.
- 100-Continue —
Expect: 100-continuehandling for large request bodies.
- Zero-allocation internals —
MemoryPool<byte>,Span<T>,ReadOnlyMemory<byte>,IBufferWriter<byte>, andSystem.Threading.Channelsthroughout the hot path - HTTP/2 multiplexing — multiple concurrent requests over a single TCP connection with header compression and per-stream flow control
- Backpressure — Akka.Streams backpressure propagates end-to-end from the network to the caller, preventing buffer bloat and memory exhaustion under load
- Channel-based API — for high-throughput scenarios, bypass
SendAsyncand write/read directly toSystem.Threading.Channelsfor pipelined I/O
- Handler pipeline — compose custom request/response transforms via
TurboHandlersubclasses or inline delegates, ordered FIFO - Distributed tracing — built-in OpenTelemetry-compatible tracing via
TracingBidiStagefor request/response lifecycle visibility - DI integration — first-class
IServiceCollectionsupport with named and typed clients,IOptionsMonitorfor runtime configuration changes - 4,200+ tests — unit tests, stream stage tests, integration tests, and benchmarks
dotnet add package TurboHTTPRequires .NET 10.0 or later.
Register and inject via dependency injection:
using TurboHTTP;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddTurboHttpClient(options =>
{
options.BaseAddress = new Uri("https://api.example.com");
options.DefaultRequestVersion = HttpVersion.Version20;
});
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<ITurboHttpClientFactory>();
var client = factory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "/users");
var response = await client.SendAsync(request);
Console.WriteLine($"Status: {response.StatusCode}");
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);Register named or typed clients with IServiceCollection:
services
.AddTurboHttpClient("GitHub", options =>
{
options.BaseAddress = new Uri("https://api.github.com");
options.ConnectTimeout = TimeSpan.FromSeconds(15);
options.IdleTimeout = TimeSpan.FromSeconds(30);
})
.WithRedirect()
.WithCookies()
.WithDecompression()
.WithRetry(new RetryPolicy { MaxRetries = 3 })
.WithCache(new CachePolicy { MaxEntries = 1000 });Then inject and use:
public class GitHubService(ITurboHttpClientFactory factory)
{
private readonly ITurboHttpClient _client = factory.CreateClient("GitHub");
public async Task<string> GetRepoAsync(string owner, string repo, CancellationToken ct)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"/repos/{owner}/{repo}");
var response = await _client.SendAsync(request, ct);
return await response.Content.ReadAsStringAsync(ct);
}
}For high-throughput scenarios, bypass SendAsync and use channels directly:
var services = new ServiceCollection();
services.AddTurboHttpClient(options =>
{
options.BaseAddress = new Uri("https://api.example.com");
});
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<ITurboHttpClientFactory>();
var client = factory.CreateClient();
// Fire requests
await client.Requests.WriteAsync(new HttpRequestMessage(HttpMethod.Get, "/ping"));
await client.Requests.WriteAsync(new HttpRequestMessage(HttpMethod.Get, "/health"));
// Read responses as they arrive
await foreach (var response in client.Responses.ReadAllAsync())
{
Console.WriteLine($"{response.RequestMessage!.RequestUri} -> {response.StatusCode}");
}Extend the pipeline with custom request/response transforms:
public sealed class AuthHandler : TurboHandler
{
public override HttpRequestMessage ProcessRequest(HttpRequestMessage request)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "my-token");
return request;
}
}
services
.AddTurboHttpClient("MyApi", options => { ... })
.AddHandler<AuthHandler>();Or use inline delegates:
services
.AddTurboHttpClient("MyApi")
.UseRequest(request =>
{
request.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
return request;
});| Option | Default | Description |
|---|---|---|
BaseAddress |
null |
Base URI for resolving relative request URIs |
ConnectTimeout |
10s | Timeout for establishing a new TCP connection |
IdleTimeout |
10s | Time a connection may remain idle before eviction |
ReconnectInterval |
5s | Delay between reconnection attempts after failure |
MaxReconnectAttempts |
10 | Max reconnection attempts before giving up |
MaxFrameSize |
128 KiB | HTTP/2 maximum frame size in bytes |
ConnectionPolicy |
null |
Per-host connection limits and HTTP/2 multiplexing settings |
DangerousAcceptAnyServerCertificate |
false |
Skip TLS validation (dev/test only) |
ServerCertificateValidationCallback |
— | Custom TLS certificate validation logic |
ClientCertificates |
null |
X.509 client certificates for mTLS |
EnabledSslProtocols |
SslProtocols.None |
TLS protocol versions to enable (OS default if None) |
Client Layer ITurboHttpClient (SendAsync / channel API)
↓
Feature Layer Akka.Streams BidiStages — outermost to innermost:
Tracing → Handlers → Redirect → Cookie → Retry →
Expect-Continue → Cache → ContentEncoding → Alt-Svc
↓
Engine Layer Engine (version router) → per-version engines
Each engine: unified ConnectionStage + NetworkBufferBatchStage
HTTP/1.0 · HTTP/1.1 · HTTP/2 · HTTP/3
↓
Protocol Layer Encoding/decoding, HPACK/QPACK, frame types — all internal
to the unified ConnectionStage per version
↓
Transport Layer TcpConnectionStage / QuicConnectionStage
├─ TCP → ConnectionManagerActor → Channel<byte> → ClientByteMover
└─ QUIC → ConnectionManagerActor → QUIC streams
For interactive architecture diagrams, see the documentation site.
Full documentation — including feature guides, architecture deep-dives, and a comparison with HttpClient — is available at https://turbohttp.st0o0.net/.
# Restore and build
dotnet restore ./src/TurboHTTP.sln
dotnet build --configuration Release ./src/TurboHTTP.sln
# Run all tests
dotnet test ./src/TurboHTTP.sln
# Run benchmarks
dotnet run --configuration Release --project ./src/TurboHTTP.Benchmarks/TurboHTTP.Benchmarks.csprojContributions are welcome. Please read CONTRIBUTING.md for branch naming conventions, PR requirements, how to run tests locally, and recommended branch protection settings.
TurboHTTP is licensed under the MIT License.