当前位置:网站首页>Explore ASP Net core read request The correct way of body
Explore ASP Net core read request The correct way of body
2022-04-23 20:45:00 【Wind god Shura envoy】
Preface
I believe you are using ASP.NET Core When it comes to development , It's going to involve reading Request.Body
Scene , After all, most of us POST All requests are to store data in Http
Of Body
among . Because the author's daily development is mainly used ASP.NET Core So I also encounter this kind of scene , About the contents of this article , From what I met in the development process about Request.Body
Read problem of . In previous use , Basically, it's all the answers to search with the help of search engines , I didn't pay much attention to this , I find that there is a big misunderstanding between my understanding and correct use . So I have feelings , I wrote this article , To record . knowledge has no limit , May I share with you .
Common reading methods
When we want to read Request Body
When , I believe your first instinct is the same as the author , It's hard , Just a few lines of code , Here we simulate in Filter
Read from Request Body
, stay Action
or Middleware
Or anywhere else , Yes Request
Where we live is Body
, As shown below
public override void OnActionExecuting(ActionExecutingContext context)
{
// stay ASP.NET Core in Request Body yes Stream In the form of
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = stream.ReadToEnd();
_logger.LogDebug("body content:" + body);
base.OnActionExecuting(context);
}
After you've written , I didn't think much about it , After all, such a routine operation , Full of confidence , Run it and debug it , I found that I directly reported a mistake System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead
. Synchronization is not allowed , Please use ReadAsync
The way or setting of AllowSynchronousIO
by true
. I didn't say how to set it up AllowSynchronousIO
, But we use search engine is our biggest strength .
Synchronous read
First, let's look at the settings AllowSynchronousIO
by true
The way , It's also known by the name that synchronization is allowed IO, There are roughly two kinds of settings , Later, we will explore the direct difference between them through the source code , Let's first look at how to set up AllowSynchronousIO
Value . The first way is in ConfigureServices
Middle configuration , The operation is as follows
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
This is the same as configuring in the configuration file Kestrel
The configuration of options is the same, but in different ways , After setting, it is ready , Running does not report an error . There's another way , You don't have to ConfigureServices
Set in , adopt IHttpBodyControlFeature
How to set , As follows
public override void OnActionExecuting(ActionExecutingContext context)
{
var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = stream.ReadToEnd();
_logger.LogDebug("body content:" + body);
base.OnActionExecuting(context);
}
It works the same way , In this way , No need to read every time Body
I always set it up , As long as you're ready to read Body
Just set it once before . Both ways are to set up AllowSynchronousIO
by true
, But we need to think a little bit , Why Microsoft set up AllowSynchronousIO
The default is false
, It shows that Microsoft does not want us to read synchronously Body
. By looking up the data, we come to the conclusion that
Kestrel
: Disabled by default AllowSynchronousIO
( Sync IO), Insufficient threads can cause the application to crash , And synchronization I/O API( for example HttpRequest.Body.Read
) Is a common cause of thread shortage .
So we can know , Although this way can solve the problem , But the performance is not bad , Microsoft doesn't recommend that either , When the program traffic is large , It's easy to cause the program to be unstable or even crash .
Asynchronous read
From the above we know that Microsoft does not want us to set up AllowSynchronousIO
The way to operate , Because it will affect performance . Then we can read it asynchronously , Asynchronous mode is actually using Stream
Own asynchronous method to read , As shown below
public override void OnActionExecuting(ActionExecutingContext context)
{
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = stream.ReadToEndAsync().GetAwaiter().GetResult();
_logger.LogDebug("body content:" + body);
base.OnActionExecuting(context);
}
It's that simple , There's no need to add anything else , Just passed ReadToEndAsync
To operate in an asynchronous way .ASP.NET Core Many of the operations in are asynchronous , Even filters or middleware can return directly Task
Method of type , So we can use asynchronous operations directly
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = await stream.ReadToEndAsync();
_logger.LogDebug("body content:" + body);
await next();
}
The advantage of these two methods is that they don't need additional settings , Just read it asynchronously , It's also our recommended practice . What's amazing is that we just StreamReader
Of ReadToEnd
Replace with ReadToEndAsync
The method is happy , Do you feel more magical . When we feel magical , It's because we don't know enough about it , Next, we'll use the source code , Step by step to uncover its mystery .
Duplicate read
Above we demonstrated using synchronous and asynchronous read RequestBody
, But is that really OK ? Not really , In this way, the correct... Can only be read once per request Body
result , If you continue to RequestBody
This Stream
To read , Nothing will be read , Let's start with an example
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = await stream.ReadToEndAsync();
_logger.LogDebug("body content:" + body);
StreamReader stream2 = new StreamReader(context.HttpContext.Request.Body);
string body2 = await stream2.ReadToEndAsync();
_logger.LogDebug("body2 content:" + body2);
await next();
}
In the example above body
There's the right thing in it RequestBody
Result , however body2
Empty string in . This situation is relatively bad , Why do you say that ? If you are Middleware
Reads the RequestBody
, The middleware is executed before model binding , This will cause the model binding to fail , Because model binding sometimes needs to read RequestBody
obtain http
Request content . As for why this is the case, I believe you have a certain understanding , Because we're reading Stream
after , At this time Stream
The pointer position is already Stream
At the end of , namely Position
Not at this time 0
, and Stream
Reading is just about relying on Position
To mark external reads Stream
To where , So when we read it again, we start at the end , You can't read any information . So we want to read repeatedly RequestBody
Then reset before reading again RequestBody
Of Position
by 0
, As shown below
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = await stream.ReadToEndAsync();
_logger.LogDebug("body content:" + body);
// Or use reset Position The way context.HttpContext.Request.Body.Position = 0;
// If you're sure it's reset after the last read Position Then this sentence can be omitted
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
StreamReader stream2 = new StreamReader(context.HttpContext.Request.Body);
string body2 = await stream2.ReadToEndAsync();
// We'll try to reset it when we're done , Fill your own hole
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
_logger.LogDebug("body2 content:" + body2);
await next();
}
After you've written , Happy to run up to see the effect , Found a mistake System.NotSupportedException: Specified method is not supported.at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.Seek(Int64 offset, SeekOrigin origin)
It is generally understood that this operation is not supported , As for why , Let's take a look at the source code later . Said so much , How to solve it ? It's also very simple. , Microsoft knows it's digging a hole , Nature offers us solutions , It's also very easy to use, that is, add EnableBuffering
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// operation Request.Body And before EnableBuffering that will do
context.HttpContext.Request.EnableBuffering();
StreamReader stream = new StreamReader(context.HttpContext.Request.Body);
string body = await stream.ReadToEndAsync();
_logger.LogDebug("body content:" + body);
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
StreamReader stream2 = new StreamReader(context.HttpContext.Request.Body);
// Note that there !!! I've used synchronous reading
string body2 = stream2.ReadToEnd();
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
_logger.LogDebug("body2 content:" + body2);
await next();
}
By adding Request.EnableBuffering()
We can read it repeatedly RequestBody
了 , We can guess the name by looking at it , He's with the cache RequestBody
of , It should be noted that Request.EnableBuffering()
To be added in preparation for reading RequestBody
It works before , Otherwise it will be invalid , And each request only needs to be added once . And you see my second reading Body
I used the synchronous way to read it RequestBody
, Isn't that amazing , Later, we will analyze this problem from the perspective of source code .
Source code exploration
Above we see through StreamReader
Of ReadToEnd
Synchronous read Request.Body
Need to set up AllowSynchronousIO
by true To operate , But use StreamReader
Of ReadToEndAsync
Methods can be operated directly .
StreamReader
and Stream
The relationship between
We see it all through operation StreamReader
It's a good way to do it , It's none of my business Request.Body
What's up , Don't worry, let's take a look at the operation here first , First of all, let's take a general look at ReadToEnd
Let's take a look at StreamReader
After all Stream
What's the connection , find ReadToEnd
Method
public override string ReadToEnd()
{
ThrowIfDisposed();
CheckAsyncTaskInProgress();
// call ReadBuffer, And then from charBuffer Extract data from .
StringBuilder sb = new StringBuilder(_charLen - _charPos);
do
{
// Loop splicing to read content
sb.Append(_charBuffer, _charPos, _charLen - _charPos);
_charPos = _charLen;
// Read buffer, This is the core operation
ReadBuffer();
} while (_charLen > 0);
// Return read content
return sb.ToString();
}
Through this source code, we learned such a message , One is StreamReader
Of ReadToEnd
In fact, the essence is to read through the loop ReadBuffer
And then through StringBuilder
To splice what's read , The core is reading ReadBuffer
Method , Because the code is more , Let's look at the core operation
if (_checkPreamble)
{
// From here we can see that the essence is to use the Stream Inside Read Method
int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos);
if (len == 0)
{
if (_byteLen > 0)
{
_charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen);
_bytePos = _byteLen = 0;
}
return _charLen;
}
_byteLen += len;
}
else
{
// From here we can see that the essence is to use the Stream Inside Read Method
_byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);
if (_byteLen == 0)
{
return _charLen;
}
}
We can learn from the above code StreamReader
It's actually a tool class , It just encapsulates right Stream
The original operation of , Simplify our code ReadToEnd
The essence of a method is to read Stream
Of Read
Method . Let's take a look ReadToEndAsync
The concrete implementation of the method
public override Task<string> ReadToEndAsync()
{
if (GetType() != typeof(StreamReader))
{
return base.ReadToEndAsync();
}
ThrowIfDisposed();
CheckAsyncTaskInProgress();
// The essence is ReadToEndAsyncInternal Method
Task<string> task = ReadToEndAsyncInternal();
_asyncReadTask = task;
return task;
}
private async Task<string> ReadToEndAsyncInternal()
{
// It is also the content read by loop splicing
StringBuilder sb = new StringBuilder(_charLen - _charPos);
do
{
int tmpCharPos = _charPos;
sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);
_charPos = _charLen;
// The core operation is ReadBufferAsync Method
await ReadBufferAsync(CancellationToken.None).ConfigureAwait(false);
} while (_charLen > 0);
return sb.ToString();
}
From this we can see that the core operation is ReadBufferAsync
Method , More code, let's also look at the core implementation
byte[] tmpByteBuffer = _byteBuffer;
//Stream Assign a value to tmpStream
Stream tmpStream = _stream;
if (_checkPreamble)
{
int tmpBytePos = _bytePos;
// The essence is to call Stream Of ReadAsync Method
int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false);
if (len == 0)
{
if (_byteLen > 0)
{
_charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen);
_bytePos = 0; _byteLen = 0;
}
return _charLen;
}
_byteLen += len;
}
else
{
// The essence is to call Stream Of ReadAsync Method
_byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);
if (_byteLen == 0)
{
return _charLen;
}
}
I can see from the above code that StreamReader
The essence of reading is reading Stream
Packaging , The core method still comes from Stream
In itself . The reason why we have introduced StreamReader
class , Just to show you StreamReader
and Stream
The relationship between , Otherwise, I'm afraid everyone will misunderstand that this wave of operation is StreamReader
The realization of in , instead of Request.Body
The problem of , In fact, it's not like this. Everything points to Stream
Of Request
Of Body
Namely Stream
You can check this for yourself , Knowing this step, we can continue .
HttpRequest
Of Body
We mentioned above Request
Of Body
Nature is Stream
,Stream
It's an abstract class , therefore Request.Body
yes Stream
Implementation class of . By default Request.Body
Yes. HttpRequestStream
Example , We said here is the default , Because it can be changed , Let's talk about it later . We're from the top StreamReader
From the conclusion of ReadToEnd
The essence is still called Stream
Of Read
Method , That is here HttpRequestStream
Of Read
Method , Let's take a look at the implementation
public override int Read(byte[] buffer, int offset, int count)
{
// Know synchronous read Body Why did you make a mistake
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
}
// The essence is to call ReadAsync
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
Through this code, we can know why we don't set AllowSynchronousIO
by true
In the case of reading Body
Will throw an exception , This is program level control , And we also learned that Read
The essence of calling ReadAsync
Asynchronous methods
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return ReadAsyncWrapper(destination, cancellationToken);
}
ReadAsync
There are no special restrictions in itself , So direct operation ReadAsync
There will be no such thing Read
It's abnormal .
From this we come to the conclusion that Request.Body
namely HttpRequestStream
Synchronous reading of Read
It throws an exception , And asynchronous reading ReadAsync
It doesn't throw an exception. It's just related to HttpRequestStream
Of Read
There is judgment in the method itself AllowSynchronousIO
It has something to do with the value of .
AllowSynchronousIO
The essence comes from
adopt HttpRequestStream
Of Read
The way we can know AllowSynchronousIO
It controls the way of synchronous reading . And we also learned AllowSynchronousIO
There are several different ways to configure , Next, let's take a general look at the essence of several ways . adopt HttpRequestStream
We know Read
Methods AllowSynchronousIO
The attribute of is from IHttpBodyControlFeature
That's the second configuration method we introduced above
private readonly HttpRequestPipeReader _pipeReader;
private readonly IHttpBodyControlFeature _bodyControl;
public HttpRequestStream(IHttpBodyControlFeature bodyControl, HttpRequestPipeReader pipeReader)
{
_bodyControl = bodyControl;
_pipeReader = pipeReader;
}
So it's with KestrelServerOptions
It must have something to do with , Because we only configure KestrelServerOptions
Yes. HttpRequestStream
Of Read
No abnormality is reported , and HttpRequestStream
Of Read
Just rely on IHttpBodyControlFeature
Of AllowSynchronousIO
attribute .Kestrel
in HttpRequestStream
The initialization place is BodyControl
private readonly HttpRequestStream _request;
public BodyControl(IHttpBodyControlFeature bodyControl, IHttpResponseControl responseControl)
{
_request = new HttpRequestStream(bodyControl, _requestReader);
}
Initialization BodyControl
Where HttpProtocol
in , We found initialization BodyControl
Of InitializeBodyControl
Method
public void InitializeBodyControl(MessageBody messageBody)
{
if (_bodyControl == null)
{
// The message here is bodyControl The relay will be this
_bodyControl = new BodyControl(bodyControl: this, this);
}
(RequestBody, ResponseBody, RequestBodyPipeReader, ResponseBodyPipeWriter) = _bodyControl.Start(messageBody);
_requestStreamInternal = RequestBody;
_responseStreamInternal = ResponseBody;
}
Here we can see initialization IHttpBodyControlFeature
Since the message is this
, That is to say HttpProtocol
The current instance . in other words HttpProtocol
Is to implement the IHttpBodyControlFeature
Interface ,HttpProtocol
Itself is partial
Of , We're in one of the distribution classes HttpProtocol.FeatureCollection
See the implementation relationship in
internal partial class HttpProtocol : IHttpRequestFeature, IHttpRequestBodyDetectionFeature, IHttpResponseFeature, IHttpResponseBodyFeature, IRequestBodyPipeFeature, IHttpUpgradeFeature, IHttpConnectionFeature, IHttpRequestLifetimeFeature, IHttpRequestIdentifierFeature, IHttpRequestTrailersFeature, IHttpBodyControlFeature, IHttpMaxRequestBodySizeFeature, IEndpointFeature, IRouteValuesFeature
{
bool IHttpBodyControlFeature.AllowSynchronousIO
{
get => AllowSynchronousIO;
set => AllowSynchronousIO = value;
}
}
From this we can see that HttpProtocol
It did IHttpBodyControlFeature
Interface , Next we find initialization AllowSynchronousIO
The place of , eureka AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
This code description comes from ServerOptions
This attribute , Found initialization ServerOptions
The place of
private HttpConnectionContext _context;
//ServiceContext Initialization comes from HttpConnectionContext
public ServiceContext ServiceContext => _context.ServiceContext;
protected KestrelServerOptions ServerOptions {
get; set; } = default!;
public void Initialize(HttpConnectionContext context)
{
_context = context;
// come from ServiceContext
ServerOptions = ServiceContext.ServerOptions;
Reset();
HttpResponseControl = this;
}
Through this we know ServerOptions
From ServiceContext
Of ServerOptions
attribute , We found it for ServiceContext
The place of assignment , stay KestrelServerImpl
Of CreateServiceContext
Simplify the logic in the method , The core content is as follows
public KestrelServerImpl(
IOptions<KestrelServerOptions> options,
IEnumerable<IConnectionListenerFactory> transportFactories,
ILoggerFactory loggerFactory)
// Infused with IOptions<KestrelServerOptions> Called CreateServiceContext
: this(transportFactories, null, CreateServiceContext(options, loggerFactory))
{
}
private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory)
{
// The value comes from IOptions<KestrelServerOptions>
var serverOptions = options.Value ?? new KestrelServerOptions();
return new ServiceContext
{
Log = trace,
HttpParser = new HttpParser<Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information)),
Scheduler = PipeScheduler.ThreadPool,
SystemClock = heartbeatManager,
DateHeaderValueManager = dateHeaderValueManager,
ConnectionManager = connectionManager,
Heartbeat = heartbeat,
// Assignment operation
ServerOptions = serverOptions,
};
}
From the above code, we can see that if it is configured KestrelServerOptions
that ServiceContext
Of ServerOptions
Attributes come from KestrelServerOptions
, That is, we go through services.Configure<KestrelServerOptions>()
The value of the configuration , All in all, we come to the conclusion that
If the KestrelServerOptions
namely services.Configure()
, that AllowSynchronousIO
From KestrelServerOptions
. namely IHttpBodyControlFeature
Of AllowSynchronousIO
Attributes come from KestrelServerOptions
. If not configured , So, directly through modification IHttpBodyControlFeature
Example of
AllowSynchronousIO
Attributes have the same effect , After all HttpRequestStream
It's directly dependent on IHttpBodyControlFeature
example .
EnableBuffering
Behind the magic
We see in the example above , If you do not add EnableBuffering
If so, set it directly RequestBody
Of Position
Will be submitted to the NotSupportedException
Such a mistake , And after adding it, I can read it directly in the synchronous way RequestBody
, First of all, let's see why we report mistakes , We know from the above mistakes that mistakes come from HttpRequestStream
This class , As we said above, this class inherits Stream
abstract class , Through the source code, we can see the following related code
// Out of commission Seek operation
public override bool CanSeek => false;
// Allow to read
public override bool CanRead => true;
// It is not allowed to write
public override bool CanWrite => false;
// Cannot get length
public override long Length => throw new NotSupportedException();
// Can't read or write Position
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
// Out of commission Seek Method
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
I believe that through these we can clearly see that HttpRequestStream
Setting or writing related operations are not allowed , That's why we're going straight up there Seek
Set up Position
Why do you report mistakes when you're in trouble , There are other operational limitations , Anyway, the default is that we don't want to HttpRequestStream
Too much operation , In particular, set or write related operations . But we use EnableBuffering
I didn't have these problems when I was young , Why on earth ? Now we're going to unveil it . First we start with Request.EnableBuffering()
This method starts with , Find the source code in HttpRequestRewindExtensions
Extension classes , We start with the simplest parameterless method and see the following definition
/// <summary>
/// Make sure Request.Body Can be read multiple times
/// </summary>
/// <param name="request"></param>
public static void EnableBuffering(this HttpRequest request)
{
BufferingHelper.EnableRewind(request);
}
The above method is the simplest form , One more EnableBuffering
The extension method of is the one with the most complete parameters , This method can control the size of the read and the limited size of the disk
/// <summary>
/// Make sure Request.Body Can be read multiple times
/// </summary>
/// <param name="request"></param>
/// <param name="bufferThreshold"> The maximum size of the buffer stream in memory ( byte ). The larger request body is written to disk .</param>
/// <param name="bufferLimit"> The maximum size of the request body ( byte ). Trying to read beyond this limit will cause an exception </param>
public static void EnableBuffering(this HttpRequest request, int bufferThreshold, long bufferLimit)
{
BufferingHelper.EnableRewind(request, bufferThreshold, bufferLimit);
}
Either way , In the end, it's all calling BufferingHelper.EnableRewind
This method , Find it without saying much BufferingHelper
This class , The code for finding the location of the class is not much, and it is relatively concise , Let's take it EnableRewind
Paste the implementation of
// The default cache size in memory is 30K, Beyond this size will be stored on disk
internal const int DefaultBufferThreshold = 1024 * 30;
/// <summary>
/// This method is also HttpRequest Extension method
/// </summary>
/// <returns></returns>
public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
// First get Request Body
var body = request.Body;
// By default Body yes HttpRequestStream This class CanSeek yes false So it's going to be done if Logic inside
if (!body.CanSeek)
{
// Instantiate FileBufferingReadStream This class , It seems that's the key
var fileStream = new FileBufferingReadStream(body, bufferThreshold,bufferLimit,AspNetCoreTempDirectory.TempDirectoryFactory);
// Assign a value to Body, In other words, it turns on EnableBuffering after Request.Body The type will be FileBufferingReadStream
request.Body = fileStream;
// Here we have to put fileStream Register with Response Easy to release
request.HttpContext.Response.RegisterForDispose(fileStream);
}
return request;
}
From the above source code implementation, we can roughly draw two conclusions
BufferingHelper
Of EnableRewind
Method is also HttpRequest
Extension method of , You can go directly through Request.EnableRewind
Form call of , The effect is the same as calling Request.EnableBuffering
because EnableBuffering
Also called EnableRewind
To enable the EnableBuffering
After this operation, you will actually use FileBufferingReadStream
Replace the default HttpRequestStream
, So the follow-up treatment RequestBody
The operation will be FileBufferingReadStream
example
Through the above analysis, we can see clearly that , The core operation is FileBufferingReadStream
This class , And it can be seen from the name that it must have inherited Stream
abstract class , What are you waiting for to find FileBufferingReadStream
The implementation of the , First, let's look at the definition of other classes
public class FileBufferingReadStream : Stream
{
}
Undoubtedly, it is inherited from Steam
class , We also see the use of Request.EnableBuffering
Then you can set and repeat the read RequestBody
, Note that there are some rewriting operations , Let's take a look at
/// <summary>
/// Allow to read
/// </summary>
public override bool CanRead
{
get {
return true; }
}
/// <summary>
/// allow Seek
/// </summary>
public override bool CanSeek
{
get {
return true; }
}
/// <summary>
/// It is not allowed to write
/// </summary>
public override bool CanWrite
{
get {
return false; }
}
/// <summary>
/// You can get the length
/// </summary>
public override long Length
{
get {
return _buffer.Length; }
}
/// <summary>
/// Can read and write Position
/// </summary>
public override long Position
{
get {
return _buffer.Position; }
set
{
ThrowIfDisposed();
_buffer.Position = value;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
// If Body Exception if released
ThrowIfDisposed();
// Special cases throw exceptions
//_completelyBuffered Represents whether the full cache must be in the original HttpRequestStream Set to... After reading true
// If the original location information is not consistent with the current location information, an exception will be thrown directly
if (!_completelyBuffered && origin == SeekOrigin.End)
{
throw new NotSupportedException("The content has not been fully buffered yet.");
}
else if (!_completelyBuffered && origin == SeekOrigin.Current && offset + Position > Length)
{
throw new NotSupportedException("The content has not been fully buffered yet.");
}
else if (!_completelyBuffered && origin == SeekOrigin.Begin && offset > Length)
{
throw new NotSupportedException("The content has not been fully buffered yet.");
}
// Recharge buffer Of Seek
return _buffer.Seek(offset, origin);
}
Because some key settings have been rewritten , So we can set some flow related operations . from Seek
In the method, we see two important parameters _completelyBuffered
and _buffer
,_completelyBuffered
To judge the original HttpRequestStream
Read complete or not , because FileBufferingReadStream
After all, I read first HttpRequestStream
The content of ._buffer
It is from HttpRequestStream
Read content , Let's take a look at the logic , Remember that this is not all logic , It's a general idea drawn out
private readonly ArrayPool<byte> _bytePool;
private const int _maxRentedBufferSize = 1024 * 1024; //1MB
private Stream _buffer;
public FileBufferingReadStream(int memoryThreshold)
{
// Even if we set up memoryThreshold Then it can't exceed 1MB Otherwise, it will also be stored on disk
if (memoryThreshold <= _maxRentedBufferSize)
{
_rentedBuffer = bytePool.Rent(memoryThreshold);
_buffer = new MemoryStream(_rentedBuffer);
_buffer.SetLength(0);
}
else
{
// exceed 1M Cache to disk, so just initialize
_buffer = new MemoryStream();
}
}
These are all initialization operations , The core operation, of course, is FileBufferingReadStream
Of Read
In the method , Because the real reading place is right here , We find Read
Method location
private readonly Stream _inner;
public FileBufferingReadStream(Stream inner)
{
// Receive the original Request.Body
_inner = inner;
}
public override int Read(Span<byte> buffer)
{
ThrowIfDisposed();
// If the read has been completed, it will be directly in buffer Get information directly back to
if (_buffer.Position < _buffer.Length || _completelyBuffered)
{
return _buffer.Read(buffer);
}
// It's not read complete that leads to this
//_inner It's the original receiving RequestBody
// Read the RequestBody Put in buffer in
var read = _inner.Read(buffer);
// If the length exceeds the set value, an exception will be thrown
if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
{
throw new IOException("Buffer limit exceeded.");
}
// If the settings are stored in memory and Body The length is greater than the set length that can be stored in memory , It's stored on disk
if (_inMemory && _memoryThreshold - read < _buffer.Length)
{
_inMemory = false;
// Cache the original Body flow
var oldBuffer = _buffer;
// Create cache file
_buffer = CreateTempFile();
// Memory limit exceeded , But no temporary files have been written yet
if (_rentedBuffer == null)
{
oldBuffer.Position = 0;
var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
try
{
// take Body Stream read to cache file stream
var copyRead = oldBuffer.Read(rentedBuffer);
// Judge whether to read to the end
while (copyRead > 0)
{
// take oldBuffer Write to cache file stream _buffer among
_buffer.Write(rentedBuffer.AsSpan(0, copyRead));
copyRead = oldBuffer.Read(rentedBuffer);
}
}
finally
{
// Return the temporary buffer to ArrayPool in
_bytePool.Return(rentedBuffer);
}
}
else
{
_buffer.Write(_rentedBuffer.AsSpan(0, (int)oldBuffer.Length));
_bytePool.Return(_rentedBuffer);
_rentedBuffer = null;
}
}
// If reading RequestBody Not to the end , Write all the way to the cache
if (read > 0)
{
_buffer.Write(buffer.Slice(0, read));
}
else
{
// If it has been read RequestBody complete , That is, after writing to the cache, it will be updated _completelyBuffered
// Mark to read all RequestBody complete , And then it's reading RequestBody Directly in _buffer Read from
_completelyBuffered = true;
}
// Return the read byte The number is used externally StreamReader Determine if the read is complete
return read;
}
There's a lot of code and it's complicated , In fact, the core idea is relatively clear , Let's summarize
- First of all, judge whether the original
RequestBody
, If it's completely readRequestBody
Then get the return directly in the buffer - If
RequestBody
The length is greater than the set memory limit , Write the buffer to the disk temporary file - If it is the first read or complete read
RequestBody
, It will beRequestBody
Write to the buffer , Until the read is complete
among CreateTempFile
This is the operation flow for creating temporary files , The purpose is to make RequestBody
Write to a temporary file . You can specify the address of the temporary file , If not specified, the system default directory will be used , Its implementation is as follows
private Stream CreateTempFile()
{
// Determine whether the cache directory has been set , If not, use the system temporary file directory
if (_tempFileDirectory == null)
{
Debug.Assert(_tempFileDirectoryAccessor != null);
_tempFileDirectory = _tempFileDirectoryAccessor();
Debug.Assert(_tempFileDirectory != null);
}
// The full path to the temporary file
_tempFileName = Path.Combine(_tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid().ToString() + ".tmp");
// Returns the operation flow of the temporary file
return new FileStream(_tempFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, 1024 * 16,
FileOptions.Asynchronous | FileOptions.DeleteOnClose | FileOptions.SequentialScan);
}
We analyzed above FileBufferingReadStream
Of Read
Method this method is a synchronous read method StreamReader
Of ReadToEnd
Methods use , Of course, it also has an asynchronous read method ReadAsync
for StreamReader
Of ReadToEndAsync
Methods use . The implementation logic of these two methods is identical , It's just that read and write operations are asynchronous , Let's not introduce that method here , Interested students can learn about it by themselves ReadAsync
Method implementation
When open EnableBuffering
When , Whether the first read is set AllowSynchronousIO
by true
Of ReadToEnd
Synchronous read mode , Or use it directly ReadToEndAsync
Asynchronous read mode of , So use it again ReadToEnd
Synchronous mode to read Request.Body
There is no need to set up AllowSynchronousIO
by true
. Because the default Request.Body
Already by HttpRequestStream
Replace the instance with FileBufferingReadStream
example , and FileBufferingReadStream
Rewrote Read
and ReadAsync
Method , There is no restriction that synchronous reading is not allowed .
summary
This article is quite long , If you want to go deep into the logic , I hope this article can give you some guidance to read the source code . In order to prevent you from going deep into the article and forgetting the specific process logic , Here we will summarize about correct reading RequestBody
The whole conclusion of
- First, about synchronous reading
Request.Body
By defaultRequestBody
The implementation ofHttpRequestStream
, howeverHttpRequestStream
In rewritingRead
Method will determine whether to turn onAllowSynchronousIO
, If not, throw an exception . howeverHttpRequestStream
OfReadAsync
There is no such limitation to the method , So read asynchronouslyRequestBody
There is no abnormal . - Though by setting
AllowSynchronousIO
Or useReadAsync
The way we can readRequestBody
, howeverRequestBody
Can't read repeatedly , This is becauseHttpRequestStream
OfPosition
andSeek
No modification is allowed , If it is set, an exception will be thrown directly . In order to be able to read repeatedly , We introducedRequest
Extension method ofEnableBuffering
In this way, we can reset the read position to achieveRequestBody
Repeat read of . - About opening
EnableBuffering
Method can be set once per request , That is, in preparation to readRequestBody
Set before . Its essence is actually to useFileBufferingReadStream
Instead of defaultRequestBody
Default type ofHttpRequestStream
, So we're at onceHttp
Operation in requestBody
In fact, it's the operationFileBufferingReadStream
, This class rewritesStream
WhenPosition
andSeek
All can be set up , In this way, we can achieve repeated reading . FileBufferingReadStream
It's not just repeatable reading , It's also added yesRequestBody
Cache function of , It makes us read repeatedly in one requestRequestBody
When you canBuffer
The cache content is directly obtained from theBuffer
Itself is aMemoryStream
. Of course, we can also implement a set of logic to replaceBody
, As long as we rewrite it so thatStream
Support to reset the read position .
版权声明
本文为[Wind god Shura envoy]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204232039329778.html
边栏推荐
- Selenium displays webdriverwait
- GSI-ECM工程建设管理数字化平台
- Devaxpress report replay: complete the drawing of conventional two-dimensional report + histogram + pie chart
- 2022dasctf APR x fat epidemic prevention challenge crypto easy_ real
- Elastic box model
- 100天拿下11K,转岗测试的超全学习指南
- Resolve the eslint warning -- ignore the warning that there is no space between the method name and ()
- Matlab: psychtoolbox installation
- LeetCode 1351、统计有序矩阵中的负数
- 3-5 obtaining cookies through XSS and the use of XSS background management system
猜你喜欢
Common problems in deploying projects with laravel and composer for PHP
[PTA] l1-002 printing hourglass
Recommended usage scenarios and production tools for common 60 types of charts
On IRP from the perspective of source code
"Meta function" of tidb 6.0: what is placement rules in SQL?
Gsi-ecm digital platform for engineering construction management
Install MySQL 5.0 under Linux 64bit 6 - the root password cannot be modified
Case of the third day of go language development fresh every day project - news release system II
PHP的Laravel与Composer部署项目时常见问题
DOS command of Intranet penetration
随机推荐
Fastdfs思维导图
Commande dos pour la pénétration de l'Intranet
MySQL基础合集
LeetCode 74、搜索二维矩阵
Leetcode 74. Search two-dimensional matrix
GO语言开发天天生鲜项目第三天 案例-新闻发布系统二
Scripy tutorial - (2) write a simple crawler
SQL gets the latest record of the data table
The construction and use of Fortress machine and springboard machine jumpserver are detailed in pictures and texts
The more you use the computer, the slower it will be? Recovery method of file accidental deletion
go struct
Resolve the error - error identifier 'attr_ id‘ is not in camel case camelcase
Elastic box model
The iswow64process function determines the number of program bits
Devaxpress report replay: complete the drawing of conventional two-dimensional report + histogram + pie chart
Leetcode 1337. Row K with the weakest combat effectiveness in the matrix
Send email to laravel
Flex layout
DOS command of Intranet penetration
3-5 obtaining cookies through XSS and the use of XSS background management system