BadHttpRequestException due to MinRequestBodyDataR

2020-07-10 03:30发布

问题:

I have an ASP.NET Core web application that accepts file uploads from authenticated users, along with some error handling to notify me when an exception occurs (Exceptional). My problem is I'm periodically getting alerts of BadHttpRequestExceptions due to users accessing the application via mobile devices in areas with unreliable coverage. I used to get this with 2.0 too, but until the exception description got updated in 2.1, I never knew it was specifically related to MinRequestBodyDataRate, or that it was configurable. I've up'd the defaults (240 bytes over 5 seconds) with the following

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel(options =>
            {
                options.Limits.MinRequestBodyDataRate = new MinDataRate(240.0, TimeSpan.FromSeconds(10.0));
            }) 
            .UseStartup<Startup>();

This doubles the duration it will wait for the client/browser, but I'm still getting error alerts. I can't control the poor reception of the users, but I'm still hoping to try and find a way to mitigate the errors. I can extend the minimum even more, but I don't want to do it for the entire application if possible. Ideally it'd be to the specific route/action handling the upload. The documentation I was able to find indicated this minimum can be configured using the IHttpMinRequestBodyDataRateFeature. I thought about putting this inside the action, but I don't think the action is even called until the modelbinding has gotten the entire file uploaded in order to bind it to my parameter.

public async Task<IActionResult> Upload(IFormFile file)
{
    var minRequestBodyDataRateFeature = HttpContext.Features.Get<IHttpMinRequestBodyDataRateFeature>();
    minRequestBodyDataRateFeature.MinDataRate = new MinDataRate(240, TimeSpan.FromSeconds(30));
    myDocumentService.SaveFile(file);
}

This is somewhat mitigated by the fact that I had previously implemented chunked uploading, and only save the file to the service/database when the last chunk comes it (building up the file gradually in a temporary location in between). But I'm still get these BadHttpRequestExceptions even with the extended duration done via CreateWebHostBuilder (chunking not shown, as it's not relevant).

I'm thinking the best bet might be to try and wire it up in the configure method that sets up the middleware, but I'm not sure about the best way to only apply it to one action. Url maybe? And I'm wondering what implications there would be if I disabled the min rate entirely by setting it to null.

I'm basically just looking to make the connection more tolerant of poor quality connections while not upsetting too much of the day to day for other aspects of the application.

  1. I need a way to apply the change (potentially in the middleware Startup.Configure()), in such a way that it only applies to the affected route/url/action.
  2. Are there implications to consider if I disable it entirely (versus enlarging it) for that route/url/action? Entire application?
  3. Failing any of that, is it safe to simply ignore these errors and never log them? Do I create a blind spot to a malicious interactions with the application/server?

回答1:

I have noticed the same issue. I have bigger files that users can download from my site and some people, depending on the region, have slow download rates and the download stops after a while. I also see the exceptions in the log.

I do not recommend to disable the feature, because of some stuck or very slow connections, that maybe sum up. Just increase the time and lower the rate to a value where you can say that your web page is still usable.

I would not do that for specific URL's. It does not hurt when the pages have the same settings unless you notice some performance issues.

Also keep in mind that there is also a MinResponseDataRate option.

The best way is to set some reasonable values at the application startup for all routes. There will always be exceptions in the log from people that losing the connection to the internet and kestrel has to close the connection after a while to free up resources.

       public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel(options =>
            {
                options.Limits.MinRequestBodyDataRate =
                    new MinDataRate(bytesPerSecond: 80, gracePeriod: TimeSpan.FromSeconds(20));
                options.Limits.MinResponseDataRate =
                    new MinDataRate(bytesPerSecond: 80, gracePeriod: TimeSpan.FromSeconds(20));
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Trace);
            })
            .UseNLog()
            .UseStartup<Startup>()
            .Build();
}

Please also have a look at your async Upload method. Make sure you have an await or return a task. I don't think it compiles this way.



回答2:

Here is an option you can do using a ResourceFilter which runs before model binding.

using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.Extensions.DependencyInjection;

namespace YourNameSpace
{
    public class RateFilter : Attribute, IResourceFilter
    {
        private const string EndPoint = "YourEndPoint";

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            try
            {
                if (!context.HttpContext.Request.Path.Value.Contains(EndPoint))
                {
                    throw new Exception($"This filter is intended to be used only on a specific end point '{EndPoint}' while it's being called from '{context.HttpContext.Request.Path.Value}'");
                }

                var minRequestRateFeature = context.HttpContext.Features.Get<IHttpMinRequestBodyDataRateFeature>();
                var minResponseRateFeature = context.HttpContext.Features.Get<IHttpMinResponseDataRateFeature>();
                //Default Bytes/s = 240, Default TimeOut = 5s

                if (minRequestRateFeature != null)
                {
                    minRequestRateFeature.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
                }

                if (minResponseRateFeature != null)
                {
                    minResponseRateFeature.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
                }
            }
            catch (Exception ex)
            {
                //Log or Throw
            }
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }
}

Then you can use the attribute on a specific end point like

        [RateFilter]
        [HttpPost]
        public IActionResult YourEndPoint(YourModel request)
        {
            return Ok();
        }
  • You can further customize the filter to take in the endpoint/rates as ctor parameters.
  • You can also remove the check for specific endpoint
  • You can return instead of throw