使用OWIN中间件修改ASP.NET WebAPI2请求体后接口仍取原始内容如何解决
问题原因
- 替换请求体为新的MemoryStream后,流的Position指针停留在写入后的末尾位置,后续WebAPI读取请求体时从末尾开始读,无法读取到清理后的内容,会 fallback 到之前缓存的原始请求数据。
- 没有同步更新请求头的
Content-Length值,清理后的内容长度和原始内容长度不一致,WebAPI会按照原始长度读取内容,出现读取异常或拿到原始数据的问题。 - 标记为async的Sanitize方法内部没有任何await操作,实际为同步执行,可能引发线程上下文异常,同时原始请求流没有正确释放,部分场景会触发流锁。
- IIS托管场景下,ASP.NET原生管道会提前缓存请求体,OWIN层修改的Body没有同步到ASP.NET内部的请求对象缓存,导致WebAPI依然读取原始数据。
解决方案
方案1:修复现有OWIN中间件
修改后的中间件代码如下:
public class SanitizerMiddleware : OwinMiddleware { public SanitizerMiddleware(OwinMiddleware next) : base(next) { } public override async Task Invoke(IOwinContext context) { if (context.Request.Method == "POST" && context.Request.Body.CanRead) { // 异步读取原始请求内容 using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8)) { string originalText = await reader.ReadToEndAsync(); // 执行内容清理逻辑 string sanitizedText = new HtmlSanitizer() .Sanitize(originalText) .Replace("'", "`") .Replace("(", "|") .Replace(")", "|"); byte[] sanitizedBytes = Encoding.UTF8.GetBytes(sanitizedText); // 生成新的请求流,重置指针到起始位置 var newBodyStream = new MemoryStream(sanitizedBytes); newBodyStream.Position = 0; context.Request.Body = newBodyStream; // 同步更新内容长度头 context.Request.Headers["Content-Length"] = sanitizedBytes.Length.ToString(); } } await Next.Invoke(context); } }
方案2:改用WebAPI原生DelegatingHandler(更稳定,适配IIS托管场景)
如果修复中间件后仍然不生效,可改用WebAPI管道内的消息处理程序,避免OWIN和ASP.NET管道的缓存冲突:
- 自定义消息处理程序
public class SanitizeRequestHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Method == HttpMethod.Post && request.Content != null) { string originalContent = await request.Content.ReadAsStringAsync(); string sanitizedContent = new HtmlSanitizer() .Sanitize(originalContent) .Replace("'", "`") .Replace("(", "|") .Replace(")", "|"); // 替换请求内容,保持原有内容类型 request.Content = new StringContent( sanitizedContent, Encoding.UTF8, request.Content.Headers.ContentType?.MediaType ?? "application/json" ); } return await base.SendAsync(request, cancellationToken); } }
- 在WebApiConfig中注册处理程序
public static class WebApiConfig { public static HttpConfiguration Register() { var config = new HttpConfiguration(); // 注册清理处理程序,插入到处理管道最前面 config.MessageHandlers.Insert(0, new SanitizeRequestHandler()); // 保留你原来的其他WebAPI配置 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); return config; } }
内容的提问来源于stack exchange,提问作者insomniac
相关产品推荐
相关产品推荐

