基于Kestrel的Web服务器延迟发送HttpResponse方案咨询
Kestrel 延迟响应请求的可行实现思路
- 基于
TaskCompletionSource实现异步挂起,避免直接留存HttpContext
请求进入自定义中间件时,先提取所有需要留存的请求信息(请求ID、请求体、鉴权标识、Header参数等)存入自定义的请求上下文对象,同时创建绑定唯一请求ID的TaskCompletionSource<T>(简称TCS),将TCS和请求上下文绑定后存入全局线程安全的请求队列。之后让请求处理流程进入await TCS.Task的挂起状态,ASP.NET Core 运行时会自动保持TCP连接存活,HttpContext在当前请求未结束前不会被回收复用。 - 跨应用通知唤醒逻辑
其他应用发送可响应通知时,携带对应请求的唯一ID,从全局队列中匹配到绑定的TCS后,调用TrySetResult(响应内容)触发任务完成,之前挂起的请求处理流程会被自动唤醒,此时可以正常读取当前请求的HttpContext写入响应内容,结束请求生命周期。 - 配套的资源管控机制
为每个入队的TCS绑定超时取消令牌,超出等待时长后自动触发超时响应,避免无效连接长期占用服务资源。请求无论正常结束、超时还是异常终止,都需要在finally块中将对应条目从全局队列中移除,避免内存泄漏。 - 规避HttpContext复用的核心注意点
绝对不要在请求挂起阶段直接持有或操作HttpContext对象,所有需要用到的请求信息必须在入队前完成提取,唤醒后操作HttpContext时仍属于当前请求的生命周期,不会出现复用导致的异常。
核心代码参考
// 自定义请求信息类 public class PendingRequest { public string RequestId { get; set; } public string RequestBody { get; set; } public CancellationToken TimeoutToken { get; set; } } // 中间件核心逻辑 app.Use(async (context, next) => { // 提前提取全部所需请求信息 var pendingRequest = new PendingRequest { RequestId = context.Request.Headers["X-Request-Id"].FirstOrDefault() ?? Guid.NewGuid().ToString("N"), RequestBody = await new StreamReader(context.Request.Body).ReadToEndAsync(), TimeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token }; var tcs = new TaskCompletionSource<(int Code, string Content)>(); // 存入线程安全的全局队列,比如ConcurrentDictionary GlobalRequestQueue.TryAdd(pendingRequest.RequestId, tcs); try { // 挂起等待通知 var resp = await tcs.Task.WaitAsync(pendingRequest.TimeoutToken); context.Response.StatusCode = resp.Code; await context.Response.WriteAsync(resp.Content); } catch (OperationCanceledException) { context.Response.StatusCode = StatusCodes.Status408RequestTimeout; await context.Response.WriteAsync("request timeout"); } finally { // 清理队列 GlobalRequestQueue.TryRemove(pendingRequest.RequestId, out _); } }); // 通知入口示例,供其他应用调用 app.MapPost("/notify/{requestId}", async (string requestId, [FromBody] NotifyContent content) => { if(GlobalRequestQueue.TryGetValue(requestId, out var tcs)) { tcs.TrySetResult((content.Code, content.Content)); } return Results.Ok(); });
内容的提问来源于stack exchange,提问作者Merrin
相关产品推荐
相关产品推荐

