.NET API:如何拒绝大Multipart请求并返回401且不关闭连接?
问题描述
客户端大文件上传代码
var content = new MultipartFormDataContent(); var stream = new FileStream("c:\\10GB.zip", FileMode.Open, FileAccess.Read); content.Add(new StreamContent(stream), "file", fileInfo.Name); request.Content = content; var response = await httpClient.SendAsync(request); if (response.IsSuccessStatusCode) return Outcome<string>.Success($"{response.StatusCode} - {await response.Content.ReadAsStringAsync()}"); var result = await response.Content.ReadAsStringAsync(); return Outcome<string>.Fail($"Failed to upload file: {response.StatusCode} - {result}");
API Token验证中间件代码
context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; await context.Response.WriteAsync("{ \"error\": \"no token found\" }");
当前遇到的问题:
- 中间件通过不调用后续中间件终止请求时,TCP连接会被关闭,客户端抛出
HttpRequestException,无法正常获取401状态码和JSON响应体。 - 询问该行为是否存在问题,以及当请求体为大文件流时,如何可靠地拒绝请求并让客户端明确知晓已被拒绝。已尝试关闭流但连接仍会终止,读取整个大文件流显然不是可行方案。
解决方案
1. 问题根源
你当前的处理方式存在两个核心问题:
- 响应内容未完全发送就终止请求,导致TCP连接被强制断开,HttpClient会将这种情况判定为网络异常,而非正常的HTTP错误响应。
- 未处理请求体的丢弃逻辑:服务端返回错误响应时,需要告知客户端停止发送剩余请求体,同时自身也要安全丢弃已接收但未处理的请求数据(无需读取完整大文件)。
2. 修正后的中间件代码
修改Token验证中间件,按以下逻辑处理:
// 设置401状态码与响应类型 context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; // 写入错误响应内容 var errorJson = "{ \"error\": \"no token found\" }"; await context.Response.WriteAsync(errorJson); // 确保响应内容完全发送到客户端 await context.Response.CompleteAsync(); // 关键:安全丢弃剩余请求体,避免资源占用 if (context.Request.ContentLength > 0 || !context.Request.Body.CanSeek) { // 告知客户端停止发送数据 context.Response.Headers.TryAdd("Transfer-Encoding", "chunked"); // 直接丢弃请求体流,无需读取全部内容 await context.Request.Body.DrainAsync(CancellationToken.None); } // 终止后续中间件执行 return;
3. 客户端异常兼容处理
HttpClient默认会在连接异常时抛出HttpRequestException,可以通过捕获异常并尝试提取响应信息来兼容:
try { var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync(); return Outcome<string>.Success($"{response.StatusCode} - {responseContent}"); } else { var errorContent = await response.Content.ReadAsStringAsync(); return Outcome<string>.Fail($"Failed to upload file: {response.StatusCode} - {errorContent}"); } } catch (HttpRequestException ex) { // 尝试从异常中提取服务端返回的响应 if (ex.InnerException is HttpProtocolException protocolEx && protocolEx.Response != null) { var errorContent = await protocolEx.Response.Content.ReadAsStringAsync(); return Outcome<string>.Fail($"Request rejected: {protocolEx.Response.StatusCode} - {errorContent}"); } // 处理其他网络异常情况 return Outcome<string>.Fail($"Network error occurred: {ex.Message}"); }
4. 额外优化建议
- 提前验证权限:客户端可以在发起大文件上传前,先发送一个轻量的Token验证请求(比如
HEAD请求),提前确认权限,避免大文件传输到一半被拒绝。 - 尽早验证Token:服务端中间件尽量在请求体开始读取前完成Token验证(比如在
HttpContext.OnStarting回调中),可以更早触发拒绝逻辑,减少不必要的网络带宽消耗。
内容的提问来源于stack exchange,提问作者Jonas Rembratt
相关产品推荐
相关产品推荐

