挡缭 发表于 2025-5-29 13:35:17

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

中间件引起的接口请求参数被拦截,导致参数一直是null,这问题困扰了我很久,值得记录

1.场景

1.1 客户端使用framework4.8做一个接口请求发送:

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);
                }
        }
}1.2 服务端


/")]
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;
        }
}1.3 写了一个中间件RequRespLogMildd ,记录请求和返回数据的日志

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 });
                        });
                }
        }

}以上中间件,在后端Program类中使用 app.UseRequestResponseLog();
不管使用客户端/postman/apifox 调用接口GetLicense时都会报错,请求的json格式一直错误,错误信息如下
{
        "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"
}1.4 错误原因是 Web API pipeline 被修改

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

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

[*]启用请求体缓冲:context.Request.EnableBuffering();
[*]读取后重置流的位置:request.Body.Position = 0;
优化后的代码:
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 });
                        });
                }
        }
}如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: net接口请求参数可能会被拦截--巨坑