找回密码
 立即注册
首页 资源区 代码 net接口请求参数可能会被拦截--巨坑

net接口请求参数可能会被拦截--巨坑

挡缭 2025-5-29 13:35:17
中间件引起的接口请求参数被拦截,导致参数一直是null,这问题困扰了我很久,值得记录

1.场景

1.1 客户端使用framework4.8做一个接口请求发送:
  1. public static class ApiHelper
  2. {
  3.         private static string Internal_ApiUrl = string.Empty;
  4.         private static string Client_ApiUrl = string.Empty;
  5.         static ApiHelper()
  6.         {
  7.                 Internal_ApiUrl = ConfigurationManager.AppSettings["Internal_ApiUrl"];
  8.                 Client_ApiUrl = ConfigurationManager.AppSettings["Client_ApiUrl"];
  9.         }
  10.         public static string GetLicenseUrl()
  11.         {
  12.                 return Internal_ApiUrl + "/Api/License/GetLicense";
  13.         }
  14.         public static WebApiCallBack GetLicense(string enterpriseName, string uniqueCode,bool IsExistLicense)
  15.         {
  16.                 FMLicense fMLicense = new FMLicense { enterpriseName = enterpriseName, uniqueCode = uniqueCode, isExistLicense = IsExistLicense };
  17.                 var jsonBody = JsonConvert.SerializeObject(fMLicense, new JsonSerializerSettings
  18.                 {
  19.                         ContractResolver = new CamelCasePropertyNamesContractResolver()
  20.                 });
  21.                 return RequestSend(GetLicenseUrl(), "POST", jsonBody);
  22.         }
  23.         public static WebApiCallBack RequestSend(string serviceUrl, string method, string bodyJson)
  24.         {
  25.                 ServicePointManager.Expect100Continue = false;
  26.                 var handler = new HttpClientHandler();
  27.                 using (var client = new HttpClient(handler))
  28.                 {
  29.                         Console.WriteLine(bodyJson);
  30.                         var content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
  31.                         content.Headers.ContentType = new MediaTypeHeaderValue("application/json");               
  32.                         var response = client.PostAsync(serviceUrl, content).Result;
  33.                         string result = response.Content.ReadAsStringAsync().Result;
  34.                         Console.WriteLine(result);
  35.                         return JsonConvert.DeserializeObject<WebApiCallBack>(result);
  36.                 }
  37.         }
  38. }
复制代码
1.2 服务端
  1. [ApiController]
  2. [Route("Api/[controller]/[action]")]
  3. public class LicenseController : ControllerBase
  4. {
  5.         private readonly IHttpContextAccessor _httpContextAccessor;
  6.         private readonly ILicenseService _licenseService;
  7.         public LicenseController(ILicenseService licenseService, IHttpContextAccessor httpContextAccessor)
  8.         {
  9.                 this._httpContextAccessor = httpContextAccessor;
  10.                 this._licenseService = licenseService;
  11.         }
  12.         [HttpPost]
  13.         public async Task<WebApiCallBack> GetLicense(FMLicense License)
  14.         {
  15.                 FMLicense fMLicense = License;
  16.                 var result = new WebApiCallBack();
  17.                 if (fMLicense == null)
  18.                 {
  19.                         result.code = GlobalStatusCodes.Status400BadRequest;
  20.                         result.msg = "实体参数为空";
  21.                         return result;
  22.                 }
  23.                 else
  24.                 {
  25.                         #region # 验证
  26.                         if (string.IsNullOrEmpty(fMLicense.enterpriseName))
  27.                         {
  28.                                 result.code = GlobalStatusCodes.Status400BadRequest;
  29.                                 result.msg = "实体参数为空";
  30.                                 result.otherData = fMLicense;
  31.                                 return result;
  32.                         }
  33.                         if (string.IsNullOrEmpty(fMLicense.uniqueCode))
  34.                         {
  35.                                 result.code = GlobalStatusCodes.Status400BadRequest;
  36.                                 result.msg = "机器唯一码不可为空!";
  37.                                 result.otherData = fMLicense;
  38.                                 return result;
  39.                         }
  40.                         #endregion
  41.                 //业务逻辑
  42.                
  43.                 return result;
  44.         }
  45. }
复制代码
1.3 写了一个中间件RequRespLogMildd ,记录请求和返回数据的日志
  1. public class RequRespLogMildd
  2. {
  3.         private readonly RequestDelegate _next;
  4.         public RequRespLogMildd(RequestDelegate next)
  5.         {
  6.                 _next = next;
  7.         }
  8.         public async Task InvokeAsync(HttpContext context)
  9.         {
  10.                 if (AppSettingsConstVars.MiddlewareRequestResponseLogEnabled)
  11.                 {
  12.                         // 过滤,只有接口
  13.                         if (context.Request.Path.Value.Contains("api") || context.Request.Path.Value.Contains("Api"))
  14.                         {
  15.                                 //context.Request.EnableBuffering();
  16.                                 Stream originalBody = context.Response.Body;
  17.                                 try
  18.                                 {
  19.                                         // 存储请求数据
  20.                                         await RequestDataLog(context);
  21.                                         using (var ms = new MemoryStream())
  22.                                         {
  23.                                                 context.Response.Body = ms;
  24.                                                 await _next(context);
  25.                                                 // 存储响应数据
  26.                                                 ResponseDataLog(context.Response, ms);
  27.                                                 ms.Position = 0;
  28.                                                 await ms.CopyToAsync(originalBody);
  29.                                         }
  30.                                 }
  31.                                 catch (Exception ex)
  32.                                 {
  33.                                         // 记录异常
  34.                                         //ErrorLogData(context.Response, ex);
  35.                                         Parallel.For(0, 1, e =>
  36.                                         {
  37.                                                 LogLockHelper.OutErrorLog("ErrorLog", "ErrorLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Request Data:", ex.Message, ex.StackTrace });
  38.                                         });
  39.                                 }
  40.                                 finally
  41.                                 {
  42.                                         context.Response.Body = originalBody;
  43.                                 }
  44.                         }
  45.                         else
  46.                         {
  47.                                 await _next(context);
  48.                         }
  49.                 }
  50.                 else
  51.                 {
  52.                         await _next(context);
  53.                 }
  54.         }
  55.         private async Task RequestDataLog(HttpContext context)
  56.         {
  57.                 var request = context.Request;
  58.                 var sr = new StreamReader(request.Body);
  59.                 var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{await sr.ReadToEndAsync()}";
  60.                 if (!string.IsNullOrEmpty(content))
  61.                 {
  62.                         Parallel.For(0, 1, e =>
  63.                         {
  64.                                 LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Request Data:", content });
  65.                         });
  66.                         //request.Body.Position = 0;
  67.                 }
  68.         }
  69.         private void ResponseDataLog(HttpResponse response, MemoryStream ms)
  70.         {
  71.                 ms.Position = 0;
  72.                 var ResponseBody = new StreamReader(ms).ReadToEnd();
  73.                 // 去除 Html
  74.                 var reg = "<[^>]+>";
  75.                 var isHtml = Regex.IsMatch(ResponseBody, reg);
  76.                 if (!string.IsNullOrEmpty(ResponseBody))
  77.                 {
  78.                         Parallel.For(0, 1, e =>
  79.                         {
  80.                                 LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Response Data:", ResponseBody });
  81.                         });
  82.                 }
  83.         }
  84. }
复制代码
以上中间件,在后端Program类中使用 app.UseRequestResponseLog();
不管使用客户端/postman/apifox 调用接口GetLicense时都会报错,请求的json格式一直错误,错误信息如下
  1. {
  2.         "errors": {
  3.                 "": [
  4.                         "A non-empty request body is required."
  5.                 ],
  6.                 "license": [
  7.                         "The License field is required."
  8.                 ]
  9.         },
  10.         "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  11.         "title": "One or more validation errors occurred.",
  12.         "status": 400,
  13.         "traceId": "00-deaf4252040738321b26fc1fd3718696-ea7ecf0fa2bb0491-00"
  14. }
复制代码
1.4 错误原因是 Web API pipeline 被修改

某些中间件可能拦截请求流(body),例如使用了某些日志中间件或反复读取 body 的 filter,可能会导致模型绑定失败。检查 Startup.cs 或 Program.cs 中是否有读取 Request.Body 的地方。
ASP.NET Core 的模型绑定器只能读取一次 HttpRequest.Body。你在 RequestDataLog() 中读取了 Body,但没有重置流的位置:
  1. var sr = new StreamReader(request.Body);
  2. var content = $"... {await sr.ReadToEndAsync()}";
复制代码
之后没有重置 request.Body.Position = 0;,所以模型绑定器读到的是空流。
2 解决方案

读取并保留请求体内容供后续使用,你需要:

  • 启用请求体缓冲:context.Request.EnableBuffering();
  • 读取后重置流的位置:request.Body.Position = 0;
优化后的代码:
  1. public class RequRespLogMildd
  2. {
  3.         private readonly RequestDelegate _next;
  4.         public RequRespLogMildd(RequestDelegate next)
  5.         {
  6.                 _next = next;
  7.         }
  8.         public async Task InvokeAsync(HttpContext context)
  9.         {
  10.                 if (AppSettingsConstVars.MiddlewareRequestResponseLogEnabled)
  11.                 {
  12.                         if (context.Request.Path.Value.Contains("api", StringComparison.OrdinalIgnoreCase))
  13.                         {
  14.                                 Stream originalBody = context.Response.Body;
  15.                                 try
  16.                                 {
  17.                                         // ✅ 启用请求体缓冲
  18.                                         context.Request.EnableBuffering();
  19.                                         // 存储请求数据
  20.                                         await RequestDataLog(context);
  21.                                         using (var ms = new MemoryStream())
  22.                                         {
  23.                                                 context.Response.Body = ms;
  24.                                                 await _next(context);
  25.                                                 // 存储响应数据
  26.                                                 ResponseDataLog(context.Response, ms);
  27.                                                 ms.Position = 0;
  28.                                                 await ms.CopyToAsync(originalBody);
  29.                                         }
  30.                                 }
  31.                                 catch (Exception ex)
  32.                                 {
  33.                                         Parallel.For(0, 1, e =>
  34.                                         {
  35.                                                 LogLockHelper.OutErrorLog("ErrorLog", "ErrorLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Request Data:", ex.Message, ex.StackTrace });
  36.                                         });
  37.                                 }
  38.                                 finally
  39.                                 {
  40.                                         context.Response.Body = originalBody;
  41.                                 }
  42.                         }
  43.                         else
  44.                         {
  45.                                 await _next(context);
  46.                         }
  47.                 }
  48.                 else
  49.                 {
  50.                         await _next(context);
  51.                 }
  52.         }
  53.         private async Task RequestDataLog(HttpContext context)
  54.         {
  55.                 var request = context.Request;
  56.                 // ✅ 读取前先设置 Position = 0
  57.                 request.Body.Position = 0;
  58.                 //// leaveOpen: true 确保读取后流还可以被使用
  59.                 //是否根据字节顺序标记(BOM)来检测编码:true(默认)检测 BOM,如果发现 BOM,则用它指定的编码代替传入的 Encoding.UTF8;false        不检测 BOM,严格使用你传入的 Encoding.UTF8。
  60.                 using var reader = new StreamReader(request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true);
  61.                 var body = await reader.ReadToEndAsync();
  62.                 // ✅ 读取完之后重置位置供后续使用
  63.                 request.Body.Position = 0;
  64.                 var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{body}";
  65.                 if (!string.IsNullOrEmpty(content))
  66.                 {
  67.                         Parallel.For(0, 1, e =>
  68.                         {
  69.                                 LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Request Data:", content });
  70.                         });
  71.                 }
  72.         }
  73.         private void ResponseDataLog(HttpResponse response, MemoryStream ms)
  74.         {
  75.                 ms.Position = 0;
  76.                 var ResponseBody = new StreamReader(ms).ReadToEnd();
  77.                 var reg = "<[^>]+>";
  78.                 var isHtml = Regex.IsMatch(ResponseBody, reg);
  79.                 if (!string.IsNullOrEmpty(ResponseBody))
  80.                 {
  81.                         Parallel.For(0, 1, e =>
  82.                         {
  83.                                 LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Response Data:", ResponseBody });
  84.                         });
  85.                 }
  86.         }
  87. }
复制代码
如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册