ooc*_*ocx 2 asp.net-web-api kestrel-http-server asp.net-core
在我的 ASP.NET Core 3.1 api 中,我将最大请求大小限制为 10 Mb:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(k =>
{
k.Limits.MaxRequestBodySize = 1024 * 1024 * 10;
});
webBuilder.UseStartup<Startup>();
});
Run Code Online (Sandbox Code Playgroud)
当请求大于 10 Mb 时,kestrel 只是关闭连接而不返回任何响应。
当请求大小超过限制时,如何返回有意义的响应?
\n\n当请求大于 10 Mb 时,kestrel 只是关闭连接而不返回任何响应。
\n
Kestrel 确实返回 413 响应,但客户端并不总是能够读取它。当 Kestrel 强制执行MaxRequestBodySizeHTTP/1.1 连接时,它会在响应 413 响应后立即关闭底层 TCP 套接字。当这种情况发生时,客户端已经承诺发送超过配置限制的响应正文。此时客户无法退出。
客户端可能无法观察到 413 响应,因为在服务器完全关闭套接字后,它仍在尝试上传太大的请求正文,从而导致客户端在读取响应之前观察到连接重置错误。
\n另一个答案使用自定义中间件响应 413 的方法可能是合理的,因为它仍然可以防止潜在昂贵的应用程序逻辑必须通过先发制人地响应 413 而不立即关闭套接字来处理大型请求客户端观察 413 而不会遇到任何连接重置错误的时间。
\n然而,其他答案的实现存在一些问题。最大的问题是用来if (context.Request.ContentLength > 10_000_000)检查请求体是否太大。HttpContext.Request.ContentLength可以为 null,并且当为 null 时context.Request.ContentLength > 10_000_000将始终返回 false。并非每个请求都预先包含 Content-Length\xe2\x80\x8b 标头。为了支持限制可能不包含此标头的分块请求和 HTTP/2 请求的大小,我们必须包装HttpContext.Request.Body\xe2\x80\x8b 并计算每次读取的大小,以确保它不超过限制。
const long RequestSizeLimit = 10_000_000;\n\nstatic Task Write413Response(HttpContext context)\n{\n if (context.Response.HasStarted)\n {\n // The status code has already been sent and cannot be changed. \n // However, rethrowing will prevent a successful chunk terminator.\n return Task.CompletedTask;\n }\n\n context.Response.StatusCode = StatusCodes.Status413PayloadTooLarge;\n context.Response.Headers["Connection"] = "close";\n return context.Response.WriteAsync("Payload Too Large");\n}\n\napp.Use(async (context, next) =>\n{\n if (context.Request.ContentLength > RequestSizeLimit)\n {\n // The client sent a Content-Length header over the limit.\n await Write413Response(context);\n }\n else if (context.Request.ContentLength is null)\n {\n // There was no Content-Length header. The request body could be any size.\n var orignalRequestBody = context.Request.Body;\n\n try\n {\n context.Request.Body = new SizeLimitedStream(context.Request.Body, RequestSizeLimit);\n await next(context);\n }\n catch (PayloadTooLargeException)\n {\n await Write413Response(context);\n throw;\n }\n finally\n {\n context.Request.Body = orignalRequestBody;\n }\n }\n else\n {\n // The client sent a Content-Length header under the limit.\n // Kestrel enforces the accuracy of the Content-Length header.\n await next(context);\n }\n});\nRun Code Online (Sandbox Code Playgroud)\n我也不建议MaxRequestBodySize通过将 Kestrel 设置为 来完全禁用 Kestrel 的限制null。它应该超过RequestSizeLimit中间件强制执行的值,因此 Kestrel 在中间件响应 413 状态代码后不会立即关闭套接字,但 Kestrel 可能不应该允许客户端发送无限量的数据作为请求正文,即使它刚刚被耗尽。
相反,我只会增加到MaxRequestBodySize中间件强制下限的两倍之类的值。无论大小限制如何,Kestrel 只会在中间件退出后关闭套接字之前最多耗尽 5 秒,因此您可能会认为,使用正确编写的中间件,Kestrel 的服务器强制请求大小限制是不必要的,但这也减轻了任何限制中间件中可能存在的错误。
builder.WebHost.ConfigureKestrel(kestrelOptions =>\n{\n // In this case, where RequestSizeLimit is 10 MB this is unnessary\n // because Kestrel\'s default limit is already 30 MB.\n options.Limits.MaxRequestBodySize = RequestSizeLimit * 2;\n});\nRun Code Online (Sandbox Code Playgroud)\n下面是SizeLimitedStream中间件使用的实现,它复制自 ASP.NET Core 的新请求解压中间件,但修改为抛出自定义异常类型,该异常类型不能与中间件抛出的其他异常混合next():
// Copied from https://github.com/dotnet/aspnetcore/blob/597413644dec9fb34bcce580cea9629a96747600/src/Middleware/RequestDecompression/src/SizeLimitedStream.cs\n// Added PayloadTooLargeException\n// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n\ninternal sealed class PayloadTooLargeException : IOException\n{\n public PayloadTooLargeException() : base("Payload Too Large") { }\n}\n\ninternal sealed class SizeLimitedStream : Stream\n{\n private readonly Stream _innerStream;\n private readonly long? _sizeLimit;\n\n private long _totalBytesRead;\n\n public SizeLimitedStream(Stream innerStream, long? sizeLimit)\n {\n if (innerStream is null)\n {\n throw new ArgumentNullException(nameof(innerStream));\n }\n\n _innerStream = innerStream;\n _sizeLimit = sizeLimit;\n }\n\n public override bool CanRead => _innerStream.CanRead;\n\n public override bool CanSeek => _innerStream.CanSeek;\n\n public override bool CanWrite => _innerStream.CanWrite;\n\n public override long Length => _innerStream.Length;\n\n public override long Position\n {\n get\n {\n return _innerStream.Position;\n }\n set\n {\n _innerStream.Position = value;\n }\n }\n\n public override void Flush()\n {\n _innerStream.Flush();\n }\n\n public override int Read(byte[] buffer, int offset, int count)\n {\n var bytesRead = _innerStream.Read(buffer, offset, count);\n\n _totalBytesRead += bytesRead;\n if (_totalBytesRead > _sizeLimit)\n {\n throw new PayloadTooLargeException();\n }\n\n return bytesRead;\n }\n\n public override long Seek(long offset, SeekOrigin origin)\n {\n return _innerStream.Seek(offset, origin);\n }\n\n public override void SetLength(long value)\n {\n _innerStream.SetLength(value);\n }\n\n public override void Write(byte[] buffer, int offset, int count)\n {\n _innerStream.Write(buffer, offset, count);\n }\n\n public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)\n {\n return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();\n }\n\n public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)\n {\n var bytesRead = await _innerStream.ReadAsync(buffer, cancellationToken);\n\n _totalBytesRead += bytesRead;\n if (_totalBytesRead > _sizeLimit)\n {\n throw new PayloadTooLargeException();\n }\n\n return bytesRead;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
2684 次 |
| 最近记录: |