net接口请求参数可能会被拦截--巨坑
<h2 id="中间件引起的接口请求参数被拦截导致参数一直是null这问题困扰了我很久值得记录">中间件引起的接口请求参数被拦截,导致参数一直是null,这问题困扰了我很久,值得记录</h2><h2 id="1场景">1.场景</h2>
<h3 id="11-客户端使用framework48做一个接口请求发送">1.1 客户端使用framework4.8做一个接口请求发送:</h3>
<pre><code>public static class ApiHelper
{
private static string Internal_ApiUrl = string.Empty;
private static string Client_ApiUrl = string.Empty;
static ApiHelper()
{
Internal_ApiUrl = ConfigurationManager.AppSettings["Internal_ApiUrl"];
Client_ApiUrl = ConfigurationManager.AppSettings["Client_ApiUrl"];
}
public static string GetLicenseUrl()
{
return Internal_ApiUrl + "/Api/License/GetLicense";
}
public static WebApiCallBack GetLicense(string enterpriseName, string uniqueCode,bool IsExistLicense)
{
FMLicense fMLicense = new FMLicense { enterpriseName = enterpriseName, uniqueCode = uniqueCode, isExistLicense = IsExistLicense };
var jsonBody = JsonConvert.SerializeObject(fMLicense, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
return RequestSend(GetLicenseUrl(), "POST", jsonBody);
}
public static WebApiCallBack RequestSend(string serviceUrl, string method, string bodyJson)
{
ServicePointManager.Expect100Continue = false;
var handler = new HttpClientHandler();
using (var client = new HttpClient(handler))
{
Console.WriteLine(bodyJson);
var content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = client.PostAsync(serviceUrl, content).Result;
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
return JsonConvert.DeserializeObject<WebApiCallBack>(result);
}
}
}
</code></pre>
<h3 id="12-服务端">1.2 服务端</h3>
<pre><code>
/")]
public class LicenseController : ControllerBase
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILicenseService _licenseService;
public LicenseController(ILicenseService licenseService, IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
this._licenseService = licenseService;
}
public async Task<WebApiCallBack> GetLicense(FMLicense License)
{
FMLicense fMLicense = License;
var result = new WebApiCallBack();
if (fMLicense == null)
{
result.code = GlobalStatusCodes.Status400BadRequest;
result.msg = "实体参数为空";
return result;
}
else
{
#region # 验证
if (string.IsNullOrEmpty(fMLicense.enterpriseName))
{
result.code = GlobalStatusCodes.Status400BadRequest;
result.msg = "实体参数为空";
result.otherData = fMLicense;
return result;
}
if (string.IsNullOrEmpty(fMLicense.uniqueCode))
{
result.code = GlobalStatusCodes.Status400BadRequest;
result.msg = "机器唯一码不可为空!";
result.otherData = fMLicense;
return result;
}
#endregion
//业务逻辑
return result;
}
}
</code></pre>
<h3 id="13-写了一个中间件requresplogmildd-记录请求和返回数据的日志">1.3 写了一个中间件RequRespLogMildd ,记录请求和返回数据的日志</h3>
<pre><code>public class RequRespLogMildd
{
private readonly RequestDelegate _next;
public RequRespLogMildd(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (AppSettingsConstVars.MiddlewareRequestResponseLogEnabled)
{
// 过滤,只有接口
if (context.Request.Path.Value.Contains("api") || context.Request.Path.Value.Contains("Api"))
{
//context.Request.EnableBuffering();
Stream originalBody = context.Response.Body;
try
{
// 存储请求数据
await RequestDataLog(context);
using (var ms = new MemoryStream())
{
context.Response.Body = ms;
await _next(context);
// 存储响应数据
ResponseDataLog(context.Response, ms);
ms.Position = 0;
await ms.CopyToAsync(originalBody);
}
}
catch (Exception ex)
{
// 记录异常
//ErrorLogData(context.Response, ex);
Parallel.For(0, 1, e =>
{
LogLockHelper.OutErrorLog("ErrorLog", "ErrorLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Request Data:", ex.Message, ex.StackTrace });
});
}
finally
{
context.Response.Body = originalBody;
}
}
else
{
await _next(context);
}
}
else
{
await _next(context);
}
}
private async Task RequestDataLog(HttpContext context)
{
var request = context.Request;
var sr = new StreamReader(request.Body);
var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{await sr.ReadToEndAsync()}";
if (!string.IsNullOrEmpty(content))
{
Parallel.For(0, 1, e =>
{
LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Request Data:", content });
});
//request.Body.Position = 0;
}
}
private void ResponseDataLog(HttpResponse response, MemoryStream ms)
{
ms.Position = 0;
var ResponseBody = new StreamReader(ms).ReadToEnd();
// 去除 Html
var reg = "<[^>]+>";
var isHtml = Regex.IsMatch(ResponseBody, reg);
if (!string.IsNullOrEmpty(ResponseBody))
{
Parallel.For(0, 1, e =>
{
LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Response Data:", ResponseBody });
});
}
}
}
</code></pre>
<p>以上中间件,在后端Program类中使用 app.UseRequestResponseLog();</p>
<p>不管使用客户端/postman/apifox 调用接口GetLicense时都会报错,请求的json格式一直错误,错误信息如下</p>
<pre><code>{
"errors": {
"": [
"A non-empty request body is required."
],
"license": [
"The License field is required."
]
},
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-deaf4252040738321b26fc1fd3718696-ea7ecf0fa2bb0491-00"
}
</code></pre>
<h3 id="14-错误原因是-web-api-pipeline-被修改">1.4 错误原因是 Web API pipeline 被修改</h3>
<p>某些中间件可能拦截请求流(body),例如使用了某些日志中间件或反复读取 body 的 filter,可能会导致模型绑定失败。检查 Startup.cs 或 Program.cs 中是否有读取 Request.Body 的地方。</p>
<p><strong>ASP.NET Core 的模型绑定器只能读取一次 HttpRequest.Body。你在 RequestDataLog() 中读取了 Body,但没有重置流的位置:</strong></p>
<pre><code>var sr = new StreamReader(request.Body);
var content = $"... {await sr.ReadToEndAsync()}";
</code></pre>
<p>之后没有重置 request.Body.Position = 0;,所以模型绑定器读到的是空流。</p>
<h2 id="2-解决方案">2 解决方案</h2>
<p>要 <strong>读取并保留请求体内容供后续使用</strong>,你需要:</p>
<ol>
<li>启用请求体缓冲:context.Request.EnableBuffering();</li>
<li>读取后重置流的位置:request.Body.Position = 0;</li>
</ol>
<p><strong>优化后的代码:</strong></p>
<pre><code>public class RequRespLogMildd
{
private readonly RequestDelegate _next;
public RequRespLogMildd(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (AppSettingsConstVars.MiddlewareRequestResponseLogEnabled)
{
if (context.Request.Path.Value.Contains("api", StringComparison.OrdinalIgnoreCase))
{
Stream originalBody = context.Response.Body;
try
{
// ✅ 启用请求体缓冲
context.Request.EnableBuffering();
// 存储请求数据
await RequestDataLog(context);
using (var ms = new MemoryStream())
{
context.Response.Body = ms;
await _next(context);
// 存储响应数据
ResponseDataLog(context.Response, ms);
ms.Position = 0;
await ms.CopyToAsync(originalBody);
}
}
catch (Exception ex)
{
Parallel.For(0, 1, e =>
{
LogLockHelper.OutErrorLog("ErrorLog", "ErrorLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Request Data:", ex.Message, ex.StackTrace });
});
}
finally
{
context.Response.Body = originalBody;
}
}
else
{
await _next(context);
}
}
else
{
await _next(context);
}
}
private async Task RequestDataLog(HttpContext context)
{
var request = context.Request;
// ✅ 读取前先设置 Position = 0
request.Body.Position = 0;
//// leaveOpen: true 确保读取后流还可以被使用
//是否根据字节顺序标记(BOM)来检测编码:true(默认)检测 BOM,如果发现 BOM,则用它指定的编码代替传入的 Encoding.UTF8;false 不检测 BOM,严格使用你传入的 Encoding.UTF8。
using var reader = new StreamReader(request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true);
var body = await reader.ReadToEndAsync();
// ✅ 读取完之后重置位置供后续使用
request.Body.Position = 0;
var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{body}";
if (!string.IsNullOrEmpty(content))
{
Parallel.For(0, 1, e =>
{
LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Request Data:", content });
});
}
}
private void ResponseDataLog(HttpResponse response, MemoryStream ms)
{
ms.Position = 0;
var ResponseBody = new StreamReader(ms).ReadToEnd();
var reg = "<[^>]+>";
var isHtml = Regex.IsMatch(ResponseBody, reg);
if (!string.IsNullOrEmpty(ResponseBody))
{
Parallel.For(0, 1, e =>
{
LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Response Data:", ResponseBody });
});
}
}
}
</code></pre>
<p><strong>如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!</strong></p><br><br>
来源:https://www.cnblogs.com/chenshibao/p/18901936
頁:
[1]