ASP.NET Core读取Request.Body的正确方法(6)

无论那种形式,最终都是在调用BufferingHelper.EnableRewind这个方法,话不多说直接找到BufferingHelper这个类,找到类的位置[点击查看源码👈]代码不多而且比较简洁,咱们就把EnableRewind的实现粘贴出来

//默认内存中可缓存的大小为30K,超过这个大小将会被存储到磁盘 internal const int DefaultBufferThreshold = 1024 * 30; /// <summary> /// 这个方法也是HttpRequest扩展方法 /// </summary> /// <returns></returns> public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null) { if (request == null) { throw new ArgumentNullException(nameof(request)); } //先获取Request Body var body = request.Body; //默认情况Body是HttpRequestStream这个类CanSeek是false所以肯定会执行到if逻辑里面 if (!body.CanSeek) { //实例化了FileBufferingReadStream这个类,看来这是关键所在 var fileStream = new FileBufferingReadStream(body, bufferThreshold,bufferLimit,AspNetCoreTempDirectory.TempDirectoryFactory); //赋值给Body,也就是说开启了EnableBuffering之后Request.Body类型将会是FileBufferingReadStream request.Body = fileStream; //这里要把fileStream注册给Response便于释放 request.HttpContext.Response.RegisterForDispose(fileStream); } return request; }

从上面这段源码实现中我们可以大致得到两个结论

BufferingHelper的EnableRewind方法也是HttpRequest的扩展方法,可以直接通过Request.EnableRewind的形式调用,效果等同于调用Request.EnableBuffering因为EnableBuffering也是调用的EnableRewind

启用了EnableBuffering这个操作之后实际上会使用FileBufferingReadStream替换掉默认的HttpRequestStream,所以后续处理RequestBody的操作将会是FileBufferingReadStream实例

通过上面的分析我们也清楚的看到了,核心操作在于FileBufferingReadStream这个类,而且从名字也能看出来它肯定是也继承了Stream抽象类,那还等啥直接找到FileBufferingReadStream的实现[点击查看源码👈],首先来看他类的定义

public class FileBufferingReadStream : Stream { }

毋庸置疑确实是继承自Steam类,我们上面也看到了使用了Request.EnableBuffering之后就可以设置和重复读取RequestBody,说明进行了一些重写操作,具体我们来看一下

/// <summary> /// 允许读 /// </summary> public override bool CanRead { get { return true; } } /// <summary> /// 允许Seek /// </summary> public override bool CanSeek { get { return true; } } /// <summary> /// 不允许写 /// </summary> public override bool CanWrite { get { return false; } } /// <summary> /// 可以获取长度 /// </summary> public override long Length { get { return _buffer.Length; } } /// <summary> /// 可以读写Position /// </summary> public override long Position { get { return _buffer.Position; } set { ThrowIfDisposed(); _buffer.Position = value; } } public override long Seek(long offset, SeekOrigin origin) { //如果Body已释放则异常 ThrowIfDisposed(); //特殊情况抛出异常 //_completelyBuffered代表是否完全缓存一定是在原始的HttpRequestStream读取完成后才置为true //出现没读取完成但是原始位置信息和当前位置信息不一致则直接抛出异常 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."); } //充值buffer的Seek return _buffer.Seek(offset, origin); }

因为重写了一些关键设置,所以我们可以设置一些流相关的操作。从Seek方法中我们看到了两个比较重要的参数_completelyBuffered和_buffer,_completelyBuffered用来判断原始的HttpRequestStream是否读取完成,因为FileBufferingReadStream归根结底还是先读取了HttpRequestStream的内容。_buffer正是承载从HttpRequestStream读取的内容,我们大致抽离一下逻辑看一下,切记这不是全部逻辑,是抽离出来的大致思想

private readonly ArrayPool<byte> _bytePool; private const int _maxRentedBufferSize = 1024 * 1024; //1MB private Stream _buffer; public FileBufferingReadStream(int memoryThreshold) { //即使我们设置memoryThreshold那么它最大也不能超过1MB否则也会存储在磁盘上 if (memoryThreshold <= _maxRentedBufferSize) { _rentedBuffer = bytePool.Rent(memoryThreshold); _buffer = new MemoryStream(_rentedBuffer); _buffer.SetLength(0); } else { //超过1M将缓存到磁盘所以仅仅初始化 _buffer = new MemoryStream(); } }

这些都是一些初始化的操作,核心操作当然还是在FileBufferingReadStream的Read方法里,因为真正读取的地方就在这,我们找到Read方法位置[]

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyfdwg.html