Skip to content

Commit 96a53e0

Browse files
committed
Enhance HTTP/2 and HTTP/3 transport layer performance and streaming
- Adjust HTTP/2 flow control window update threshold for more efficient `WINDOW_UPDATE` frames. - Optimize HPACK dynamic table lookups for header encoding by introducing an O(1) indexed cache. - Batch multiple HTTP/2 outbound frames into a single network buffer write to reduce overhead. - Improve HTTP/3 request body encoding to support streaming for unknown content lengths, preventing excessive memory allocation.
1 parent 06a2ce5 commit 96a53e0

15 files changed

Lines changed: 216 additions & 360 deletions

File tree

src/TurboHTTP.AcceptanceTests/H10/ResilienceSpec.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public async Task Resilience_should_fail_gracefully_on_corrupt_brotli()
125125
await response.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken);
126126
}
127127

128-
[Fact(Timeout = 5000)]
128+
[Fact(Timeout = 10000)]
129129
[Trait("RFC", "RFC1945-7.2")]
130130
public async Task Resilience_should_detect_truncated_body()
131131
{
@@ -156,7 +156,7 @@ public async Task Resilience_should_detect_truncated_body()
156156

157157
await Assert.ThrowsAnyAsync<Exception>(async () =>
158158
{
159-
var response = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken);
159+
var response = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
160160
await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
161161
});
162162
}

src/TurboHTTP.IntegrationTests/H10/CacheSpec.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public CacheSpec(ServerFixture server, ActorSystemFixture systemFixture)
1717
_systemFixture = systemFixture;
1818
}
1919

20-
private ClientHelper CreateCacheClient(CacheStore store, CachePolicy? policy = null)
20+
private ClientHelper CreateCacheClient(ICacheStore store, CachePolicy? policy = null)
2121
{
2222
return ClientHelper.CreateClient(
2323
_server.H1Port,
@@ -35,7 +35,7 @@ private ClientHelper CreateCacheClient(CacheStore store, CachePolicy? policy = n
3535
public async Task Cache_should_serve_max_age_response_from_cache()
3636
{
3737
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
38-
var store = new CacheStore(CachePolicy.Default);
38+
var store = new MemoryCacheStore();
3939

4040
await using var helper = CreateCacheClient(store);
4141

@@ -57,7 +57,7 @@ public async Task Cache_should_serve_max_age_response_from_cache()
5757
public async Task Cache_should_force_revalidation_with_no_cache()
5858
{
5959
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
60-
var store = new CacheStore(CachePolicy.Default);
60+
var store = new MemoryCacheStore();
6161

6262
await using var helper = CreateCacheClient(store);
6363

@@ -81,7 +81,7 @@ public async Task Cache_should_force_revalidation_with_no_cache()
8181
public async Task Cache_should_never_cache_no_store_response()
8282
{
8383
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
84-
var store = new CacheStore(CachePolicy.Default);
84+
var store = new MemoryCacheStore();
8585

8686
await using var helper = CreateCacheClient(store);
8787

@@ -105,7 +105,7 @@ public async Task Cache_should_never_cache_no_store_response()
105105
public async Task Cache_should_send_if_none_match_for_etag_revalidation()
106106
{
107107
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
108-
var store = new CacheStore(CachePolicy.Default);
108+
var store = new MemoryCacheStore();
109109

110110
await using var helper = CreateCacheClient(store);
111111

@@ -128,7 +128,7 @@ public async Task Cache_should_send_if_none_match_for_etag_revalidation()
128128
public async Task Cache_should_send_if_modified_since_for_last_modified_revalidation()
129129
{
130130
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
131-
var store = new CacheStore(CachePolicy.Default);
131+
var store = new MemoryCacheStore();
132132

133133
await using var helper = CreateCacheClient(store);
134134

@@ -151,7 +151,7 @@ public async Task Cache_should_send_if_modified_since_for_last_modified_revalida
151151
public async Task Cache_should_produce_different_entries_for_vary_header_values()
152152
{
153153
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
154-
var store = new CacheStore(CachePolicy.Default);
154+
var store = new MemoryCacheStore();
155155

156156
await using var helper = CreateCacheClient(store);
157157

@@ -187,7 +187,7 @@ public async Task Cache_should_produce_different_entries_for_vary_header_values(
187187
public async Task Cache_should_force_revalidation_when_must_revalidate()
188188
{
189189
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
190-
var store = new CacheStore(CachePolicy.Default);
190+
var store = new MemoryCacheStore();
191191

192192
await using var helper = CreateCacheClient(store);
193193

@@ -211,7 +211,7 @@ public async Task Cache_should_respect_s_maxage_by_shared_cache()
211211
{
212212
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
213213
var policy = new CachePolicy { SharedCache = true };
214-
var store = new CacheStore(policy);
214+
var store = new MemoryCacheStore();
215215

216216
await using var helper = CreateCacheClient(store, policy);
217217

@@ -232,7 +232,7 @@ public async Task Cache_should_respect_s_maxage_by_shared_cache()
232232
public async Task Cache_should_enable_caching_with_expires_header()
233233
{
234234
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
235-
var store = new CacheStore(CachePolicy.Default);
235+
var store = new MemoryCacheStore();
236236

237237
await using var helper = CreateCacheClient(store);
238238

@@ -253,7 +253,7 @@ public async Task Cache_should_enable_caching_with_expires_header()
253253
public async Task Cache_should_cache_private_response_by_private_cache()
254254
{
255255
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
256-
var store = new CacheStore(CachePolicy.Default);
256+
var store = new MemoryCacheStore();
257257

258258
await using var helper = CreateCacheClient(store);
259259

@@ -276,7 +276,7 @@ public async Task Cache_must_not_cache_private_response_by_shared_cache()
276276
{
277277
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
278278
var policy = new CachePolicy { SharedCache = true };
279-
var store = new CacheStore(policy);
279+
var store = new MemoryCacheStore();
280280

281281
await using var helper = CreateCacheClient(store, policy);
282282

src/TurboHTTP.IntegrationTests/H10/CookieSpec.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public CookieSpec(ServerFixture server, ActorSystemFixture systemFixture)
1818
_systemFixture = systemFixture;
1919
}
2020

21-
private ClientHelper CreateCookieClient(CookieJar jar)
21+
private ClientHelper CreateCookieClient(MemoryCookieStore jar)
2222
{
2323
return ClientHelper.CreateClient(
2424
_server.H1Port,
@@ -31,7 +31,7 @@ private ClientHelper CreateCookieClient(CookieJar jar)
3131
public async Task Cookie_should_roundtrip_set_and_echo()
3232
{
3333
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
34-
var jar = new CookieJar();
34+
var jar = new MemoryCookieStore();
3535

3636
await using var helper = CreateCookieClient(jar);
3737

@@ -52,7 +52,7 @@ public async Task Cookie_should_roundtrip_set_and_echo()
5252
public async Task Cookie_must_not_be_sent_over_plaintext_when_secure()
5353
{
5454
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
55-
var jar = new CookieJar();
55+
var jar = new MemoryCookieStore();
5656

5757
await using var helper = CreateCookieClient(jar);
5858

@@ -73,7 +73,7 @@ public async Task Cookie_must_not_be_sent_over_plaintext_when_secure()
7373
public async Task Cookie_should_send_httponly_on_subsequent_requests()
7474
{
7575
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
76-
var jar = new CookieJar();
76+
var jar = new MemoryCookieStore();
7777

7878
await using var helper = CreateCookieClient(jar);
7979

@@ -97,7 +97,7 @@ public async Task Cookie_should_send_httponly_on_subsequent_requests()
9797
public async Task Cookie_should_store_and_send_samesite(string policy)
9898
{
9999
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
100-
var jar = new CookieJar();
100+
var jar = new MemoryCookieStore();
101101

102102
await using var helper = CreateCookieClient(jar);
103103

@@ -118,7 +118,7 @@ public async Task Cookie_should_store_and_send_samesite(string policy)
118118
public async Task Cookie_should_not_be_sent_after_max_age_expires()
119119
{
120120
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(25));
121-
var jar = new CookieJar();
121+
var jar = new MemoryCookieStore();
122122

123123
await using var helper = CreateCookieClient(jar);
124124

@@ -149,7 +149,7 @@ public async Task Cookie_should_not_be_sent_after_max_age_expires()
149149
public async Task Cookie_should_be_stored_when_domain_scoped()
150150
{
151151
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
152-
var jar = new CookieJar();
152+
var jar = new MemoryCookieStore();
153153

154154
await using var helper = CreateCookieClient(jar);
155155

@@ -170,7 +170,7 @@ public async Task Cookie_should_be_stored_when_domain_scoped()
170170
public async Task Cookie_should_be_sent_for_matching_path_when_path_scoped()
171171
{
172172
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
173-
var jar = new CookieJar();
173+
var jar = new MemoryCookieStore();
174174

175175
await using var helper = CreateCookieClient(jar);
176176

@@ -193,7 +193,7 @@ public async Task Cookie_should_be_sent_for_matching_path_when_path_scoped()
193193
public async Task Cookie_should_return_empty_when_no_cookies_set()
194194
{
195195
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
196-
var jar = new CookieJar();
196+
var jar = new MemoryCookieStore();
197197

198198
await using var helper = CreateCookieClient(jar);
199199

@@ -210,7 +210,7 @@ public async Task Cookie_should_return_empty_when_no_cookies_set()
210210
public async Task Cookie_should_store_all_multiple_setcookie_headers()
211211
{
212212
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
213-
var jar = new CookieJar();
213+
var jar = new MemoryCookieStore();
214214

215215
await using var helper = CreateCookieClient(jar);
216216

@@ -233,7 +233,7 @@ public async Task Cookie_should_store_all_multiple_setcookie_headers()
233233
public async Task Cookie_should_be_deleted_via_max_age_zero()
234234
{
235235
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(25));
236-
var jar = new CookieJar();
236+
var jar = new MemoryCookieStore();
237237

238238
await using var helper = CreateCookieClient(jar);
239239

@@ -266,13 +266,13 @@ public async Task Cookie_should_be_deleted_via_max_age_zero()
266266
public async Task Cookie_should_persist_across_redirect_response()
267267
{
268268
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
269-
var jar = new CookieJar();
269+
var jar = new MemoryCookieStore();
270270

271271
await using var helper = CreateCookieClient(jar);
272272

273273
// This route sets a cookie and returns a 302 redirect to /cookie/echo.
274274
// Without automatic redirect following, we get the 302 back —
275-
// but the CookieJar should still store the Set-Cookie from the response.
275+
// but the MemoryCookieStore should still store the Set-Cookie from the response.
276276
var setRequest = new HttpRequestMessage(HttpMethod.Get, "/cookie/set-and-redirect");
277277
var setResponse = await helper.Client.SendAsync(setRequest, cts.Token);
278278
Assert.Equal(HttpStatusCode.Found, setResponse.StatusCode);

src/TurboHTTP.IntegrationTests/H11/HandlerPipelineSpec.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public async Task HandlerPipeline_should_work_with_cookie_pipeline()
211211
// UseRequest injects X-From-Handler.
212212
// WithCookies causes the jar to inject a Cookie header on subsequent requests.
213213
// /interaction/echo-all-headers echoes X-* headers AND Cookie as X-Received-Cookie.
214-
var jar = new CookieJar();
214+
var jar = new MemoryCookieStore();
215215

216216
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
217217
await using var helper = ClientHelper.CreateClient(

src/TurboHTTP.IntegrationTests/H3/HandlerPipelineSpec.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ public async Task HandlerPipeline_should_work_with_compression_pipeline()
221221
[Fact(Timeout = 20000)]
222222
public async Task HandlerPipeline_should_work_with_cookie_pipeline()
223223
{
224-
var jar = new CookieJar();
224+
var jar = new MemoryCookieStore();
225225

226226
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
227227
await using var helper = ClientHelper.CreateClient(

0 commit comments

Comments
 (0)