日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

深入探究ASP.NET Core读取Request.Body的正确方式

發(fā)布時間:2023/12/4 asp.net 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入探究ASP.NET Core读取Request.Body的正确方式 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

????相信大家在使用ASP.NET Core進行開發(fā)的時候,肯定會涉及到讀取Request.Body的場景,畢竟我們大部分的POST請求都是將數(shù)據(jù)存放到Http的Body當中。因為筆者日常開發(fā)所使用的主要也是ASP.NET Core所以筆者也遇到這這種場景,關于本篇文章所套路的內(nèi)容,來自于在開發(fā)過程中我遇到的關于Request.Body的讀取問題。在之前的使用的時候,基本上都是借助搜索引擎搜索的答案,并沒有太關注這個,發(fā)現(xiàn)自己理解的和正確的使用之間存在很大的誤區(qū)。故有感而發(fā),便寫下此文,以作記錄。學無止境,愿與君共勉。

常用讀取方式

當我們要讀取Request Body的時候,相信大家第一直覺和筆者是一樣的,這有啥難的,直接幾行代碼寫完,這里我們模擬在Filter中讀取Request Body,在Action或Middleware或其他地方讀取類似,有Request的地方就有Body,如下所示

public override void OnActionExecuting(ActionExecutingContext context) {//在ASP.NET Core中Request Body是Stream的形式StreamReader stream = new StreamReader(context.HttpContext.Request.Body);string body = stream.ReadToEnd();_logger.LogDebug("body content:" + body);base.OnActionExecuting(context); }

寫完之后,也沒多想,畢竟這么常規(guī)的操作,信心滿滿,運行起來調(diào)試一把,發(fā)現(xiàn)直接報一個這個錯System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.大致的意思就是同步操作不被允許,請使用ReadAsync的方式或設置AllowSynchronousIO為true。雖然沒說怎么設置AllowSynchronousIO,不過我們借助搜索引擎是我們最大的強項。

同步讀取

首先我們來看設置AllowSynchronousIO為true的方式,看名字也知道是允許同步IO,設置方式大致有兩種,待會我們會通過源碼來探究一下它們直接有何不同,我們先來看一下如何設置AllowSynchronousIO的值。第一種方式是在ConfigureServices中配置,操作如下

services.Configure<KestrelServerOptions>(options => {options.AllowSynchronousIO = true; });

這種方式和在配置文件中配置Kestrel選項配置是一樣的只是方式不同,設置完之后即可,運行不在報錯。還有一種方式,可以不用在ConfigureServices中設置,通過IHttpBodyControlFeature的方式設置,具體如下

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

這種方式同樣有效,通過這種方式操作,不需要每次讀取Body的時候都去設置,只要在準備讀取Body之前設置一次即可。這兩種方式都是去設置AllowSynchronousIO為true,但是我們需要思考一點,微軟為何設置AllowSynchronousIO默認為false,說明微軟并不希望我們?nèi)ネ阶x取Body。通過查找資料得出了這么一個結論

Kestrel:默認情況下禁用 AllowSynchronousIO(同步IO),線程不足會導致應用崩潰,而同步I/O API(例如HttpRequest.Body.Read)是導致線程不足的常見原因。

由此可以知道,這種方式雖然能解決問題,但是性能并不是不好,微軟也不建議這么操作,當程序流量比較大的時候,很容易導致程序不穩(wěn)定甚至崩潰。

異步讀取

通過上面我們了解到微軟并不希望我們通過設置AllowSynchronousIO的方式去操作,因為會影響性能。那我們可以使用異步的方式去讀取,這里所說的異步方式其實就是使用Stream自帶的異步方法去讀取,如下所示

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

就這么簡單,不需要額外設置其他的東西,僅僅通過ReadToEndAsync的異步方法去操作。ASP.NET Core中許多操作都是異步操作,甚至是過濾器或中間件都可以直接返回Task類型的方法,因此我們可以直接使用異步操作

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

這兩種方式的操作優(yōu)點是不需要額外設置別的,只是通過異步方法讀取即可,也是我們比較推薦的做法。比較神奇的是我們只是將StreamReader的ReadToEnd替換成ReadToEndAsync方法就皆大歡喜了,有沒有感覺到比較神奇。當我們感到神奇的時候,是因為我們對它還不夠了解,接下來我們就通過源碼的方式,一步一步的揭開它神秘的面紗。

重復讀取

上面我們演示了使用同步方式和異步方式讀取RequestBody,但是這樣真的就可以了嗎?其實并不行,這種方式每次請求只能讀取一次正確的Body結果,如果繼續(xù)對RequestBody這個Stream進行讀取,將讀取不到任何內(nèi)容,首先來舉個例子

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

上面的例子中body里有正確的RequestBody的結果,但是body2中是空字符串。這個情況是比較糟糕的,為啥這么說呢?如果你是在Middleware中讀取的RequestBody,而這個中間件的執(zhí)行是在模型綁定之前,那么將會導致模型綁定失敗,因為模型綁定有的時候也需要讀取RequestBody獲取http請求內(nèi)容。至于為什么會這樣相信大家也有了一定的了解,因為我們在讀取完Stream之后,此時的Stream指針位置已經(jīng)在Stream的結尾處,即Position此時不為0,而Stream讀取正是依賴Position來標記外部讀取Stream到啥位置,所以我們再次讀取的時候會從結尾開始讀,也就讀取不到任何信息了。所以我們要想重復讀取RequestBody那么就要再次讀取之前重置RequestBody的Position為0,如下所示

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);//或者使用重置Position的方式 context.HttpContext.Request.Body.Position = 0;//如果你確定上次讀取完之后已經(jīng)重置了Position那么這一句可以省略context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);StreamReader stream2 = new StreamReader(context.HttpContext.Request.Body);string body2 = await stream2.ReadToEndAsync();//用完了我們盡量也重置一下,自己的坑自己填context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);_logger.LogDebug("body2 content:" + body2);await next(); }

寫完之后,開開心心的運行起來看一下效果,發(fā)現(xiàn)報了一個錯System.NotSupportedException: Specified method is not supported.at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.Seek(Int64 offset, SeekOrigin origin)大致可以理解起來不支持這個操作,至于為啥,一會解析源碼的時候咱們一起看一下。說了這么多,那到底該如何解決呢?也很簡單,微軟知道自己刨下了坑,自然給我們提供了解決辦法,用起來也很簡單就是加EnableBuffering

public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {//操作Request.Body之前加上EnableBuffering即可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);//注意這里!!!我已經(jīng)使用了同步讀取的方式string body2 = stream2.ReadToEnd();context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);_logger.LogDebug("body2 content:" + body2);await next(); }

通過添加Request.EnableBuffering()我們就可以重復的讀取RequestBody了,看名字我們可以大概的猜出來,他是和緩存RequestBody有關,需要注意的是Request.EnableBuffering()要加在準備讀取RequestBody之前才有效果,否則將無效,而且每次請求只需要添加一次即可。而且大家看到了我第二次讀取Body的時候使用了同步的方式去讀取的RequestBody,是不是很神奇,待會的時候我們會從源碼的角度分析這個問題。

源碼探究

上面我們看到了通過StreamReader的ReadToEnd同步讀取Request.Body需要設置AllowSynchronousIO為true才能操作,但是使用StreamReader的ReadToEndAsync方法卻可以直接操作。

StreamReader和Stream的關系

我們看到了都是通過操作StreamReader的方法即可,那關我Request.Body啥事,別急咱們先看一看這里的操作,首先來大致看下ReadToEnd的實現(xiàn)了解一下StreamReader到底和Stream有啥關聯(lián),找到ReadToEnd方法[點擊查看源碼????[1]]

public override string ReadToEnd() {ThrowIfDisposed();CheckAsyncTaskInProgress();// 調(diào)用ReadBuffer,然后從charBuffer中提取數(shù)據(jù)。StringBuilder sb = new StringBuilder(_charLen - _charPos);do{//循環(huán)拼接讀取內(nèi)容sb.Append(_charBuffer, _charPos, _charLen - _charPos);_charPos = _charLen; //讀取buffer,這是核心操作ReadBuffer();} while (_charLen > 0);//返回讀取內(nèi)容return sb.ToString(); }

通過這段源碼我們了解到了這么個信息,一個是StreamReader的ReadToEnd其實本質(zhì)是通過循環(huán)讀取ReadBuffer然后通過StringBuilder去拼接讀取的內(nèi)容,核心是讀取ReadBuffer方法,由于代碼比較多,我們找到大致呈現(xiàn)一下核心操作[點擊查看源碼????[2]]

if (_checkPreamble) {//通過這里我們可以知道本質(zhì)就是使用要讀取的Stream里的Read方法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 {//通過這里我們可以知道本質(zhì)就是使用要讀取的Stream里的Read方法_byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length);if (_byteLen == 0) {return _charLen;} }

通過上面的代碼我們可以了解到StreamReader其實是工具類,只是封裝了對Stream的原始操作,簡化我們的代碼ReadToEnd方法本質(zhì)是讀取Stream的Read方法。接下來我們看一下ReadToEndAsync方法的具體實現(xiàn)[點擊查看源碼????[3]]

public override Task<string> ReadToEndAsync() {if (GetType() != typeof(StreamReader)){return base.ReadToEndAsync();}ThrowIfDisposed();CheckAsyncTaskInProgress();//本質(zhì)是ReadToEndAsyncInternal方法Task<string> task = ReadToEndAsyncInternal();_asyncReadTask = task;return task; }private async Task<string> ReadToEndAsyncInternal() {//也是循環(huán)拼接讀取的內(nèi)容StringBuilder sb = new StringBuilder(_charLen - _charPos);do{int tmpCharPos = _charPos;sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos);_charPos = _charLen; //核心操作是ReadBufferAsync方法await ReadBufferAsync(CancellationToken.None).ConfigureAwait(false);} while (_charLen > 0);return sb.ToString(); }

通過這個我們可以看到核心操作是ReadBufferAsync方法,代碼比較多我們同樣看一下核心實現(xiàn)[點擊查看源碼????[4]]

byte[] tmpByteBuffer = _byteBuffer; //Stream賦值給tmpStream Stream tmpStream = _stream; if (_checkPreamble) {int tmpBytePos = _bytePos;//本質(zhì)是調(diào)用Stream的ReadAsync方法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 {//本質(zhì)是調(diào)用Stream的ReadAsync方法_byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false);if (_byteLen == 0) {return _charLen;} }

通過上面代碼我可以了解到StreamReader的本質(zhì)就是讀取Stream的包裝,核心方法還是來自Stream本身。我們之所以大致介紹了StreamReader類,就是為了給大家呈現(xiàn)出StreamReader和Stream的關系,否則怕大家誤解這波操作是StreamReader的里的實現(xiàn),而不是Request.Body的問題,其實并不是這樣的所有的一切都是指向Stream的Request的Body就是Stream這個大家可以自己查看一下,了解到這一步我們就可以繼續(xù)了。

HttpRequest的Body

上面我們說到了Request的Body本質(zhì)就是Stream,Stream本身是抽象類,所以Request.Body是Stream的實現(xiàn)類。默認情況下Request.Body的是HttpRequestStream的實例[點擊查看源碼????[5]],我們這里說了是默認,因為它是可以改變的,我們一會再說。我們從上面StreamReader的結論中得到ReadToEnd本質(zhì)還是調(diào)用的Stream的Read方法,即這里的HttpRequestStream的Read方法,我們來看一下具體實現(xiàn)[點擊查看源碼????[6]]

public override int Read(byte[] buffer, int offset, int count) {//知道同步讀取Body為啥報錯了吧if (!_bodyControl.AllowSynchronousIO){throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);}//本質(zhì)是調(diào)用ReadAsyncreturn ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); }

通過這段代碼我們就可以知道了為啥在不設置AllowSynchronousIO為true的情下讀取Body會拋出異常了吧,這個是程序級別的控制,而且我們還了解到Read的本質(zhì)還是在調(diào)用ReadAsync異步方法

public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default) {return ReadAsyncWrapper(destination, cancellationToken); }

ReadAsync本身并無特殊限制,所以直接操作ReadAsync不會存在類似Read的異常。

通過這個我們得出了結論Request.Body即HttpRequestStream的同步讀取Read會拋出異常,而異步讀取ReadAsync并不會拋出異常只和HttpRequestStream的Read方法本身存在判斷AllowSynchronousIO的值有關系。

AllowSynchronousIO本質(zhì)來源

通過HttpRequestStream的Read方法我們可以知道AllowSynchronousIO控制了同步讀取的方式。而且我們還了解到了AllowSynchronousIO有幾種不同方式的去配置,接下來我們來大致看下幾種方式的本質(zhì)是哪一種。通過HttpRequestStream我們知道Read方法中的AllowSynchronousIO的屬性是來自IHttpBodyControlFeature也就是我們上面介紹的第二種配置方式

private readonly HttpRequestPipeReader _pipeReader; private readonly IHttpBodyControlFeature _bodyControl; public HttpRequestStream(IHttpBodyControlFeature bodyControl, HttpRequestPipeReader pipeReader) {_bodyControl = bodyControl;_pipeReader = pipeReader; }

那么它和KestrelServerOptions肯定是有關系的,因為我們只配置KestrelServerOptions的是HttpRequestStream的Read是不報異常的,而HttpRequestStream的Read只依賴了IHttpBodyControlFeature的AllowSynchronousIO屬性。Kestrel中HttpRequestStream初始化的地方在BodyControl[點擊查看源碼????[7]]

private readonly HttpRequestStream _request; public BodyControl(IHttpBodyControlFeature bodyControl, IHttpResponseControl responseControl) {_request = new HttpRequestStream(bodyControl, _requestReader); }

而初始化BodyControl的地方在HttpProtocol中,我們找到初始化BodyControl的InitializeBodyControl方法[點擊查看源碼????[8]]

public void InitializeBodyControl(MessageBody messageBody) {if (_bodyControl == null){//這里傳遞的是bodyControl傳遞的是this_bodyControl = new BodyControl(bodyControl: this, this);}(RequestBody, ResponseBody, RequestBodyPipeReader, ResponseBodyPipeWriter) = _bodyControl.Start(messageBody);_requestStreamInternal = RequestBody;_responseStreamInternal = ResponseBody; }

這里我們可以看的到初始化IHttpBodyControlFeature既然傳遞的是this,也就是HttpProtocol當前實例。也就是說HttpProtocol是實現(xiàn)了IHttpBodyControlFeature接口,HttpProtocol本身是partial的,我們在其中一個分布類HttpProtocol.FeatureCollection中看到了實現(xiàn)關系 [點擊查看源碼????[9]]

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

通過這個可以看出HttpProtocol確實實現(xiàn)了IHttpBodyControlFeature接口,接下來我們找到初始化AllowSynchronousIO的地方,找到了AllowSynchronousIO = ServerOptions.AllowSynchronousIO;這段代碼說明來自于ServerOptions這個屬性,找到初始化ServerOptions的地方[點擊查看源碼????[10]]

private HttpConnectionContext _context; //ServiceContext初始化來自HttpConnectionContext public ServiceContext ServiceContext => _context.ServiceContext; protected KestrelServerOptions ServerOptions { get; set; } = default!; public void Initialize(HttpConnectionContext context) {_context = context;//來自ServiceContextServerOptions = ServiceContext.ServerOptions;Reset();HttpResponseControl = this; }

通過這個我們知道ServerOptions來自于ServiceContext的ServerOptions屬性,我們找到給ServiceContext賦值的地方,在KestrelServerImpl的CreateServiceContext方法里[點擊查看源碼????[11]]精簡一下邏輯,抽出來核心內(nèi)容大致實現(xiàn)如下

public KestrelServerImpl(IOptions<KestrelServerOptions> options,IEnumerable<IConnectionListenerFactory> transportFactories,ILoggerFactory loggerFactory) //注入進來的IOptions<KestrelServerOptions>調(diào)用了CreateServiceContext: this(transportFactories, null, CreateServiceContext(options, loggerFactory)) { }private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory) {//值來自于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,//賦值操作ServerOptions = serverOptions,}; }

通過上面的代碼我們可以看到如果配置了KestrelServerOptions那么ServiceContext的ServerOptions屬性就來自于KestrelServerOptions,即我們通過services.Configure<KestrelServerOptions>()配置的值,總之得到了這么一個結論

如果配置了KestrelServerOptions即services.Configure(),那么AllowSynchronousIO來自于KestrelServerOptions。即IHttpBodyControlFeature的AllowSynchronousIO屬性來自于KestrelServerOptions。如果沒有配置,那么直接通過修改IHttpBodyControlFeature實例的 AllowSynchronousIO屬性能得到相同的效果,畢竟HttpRequestStream是直接依賴的IHttpBodyControlFeature實例。

EnableBuffering神奇的背后

我們在上面的示例中看到了,如果不添加EnableBuffering的話直接設置RequestBody的Position會報NotSupportedException這么一個錯誤,而且加了它之后我居然可以直接使用同步的方式去讀取RequestBody,首先我們來看一下為啥會報錯,我們從上面的錯誤了解到錯誤來自于HttpRequestStream這個類[點擊查看源碼????[12]],上面我們也說了這個類繼承了Stream抽象類,通過源碼我們可以看到如下相關代碼

//不能使用Seek操作 public override bool CanSeek => false; //允許讀 public override bool CanRead => true; //不允許寫 public override bool CanWrite => false; //不能獲取長度 public override long Length => throw new NotSupportedException(); //不能讀寫Position public override long Position {get => throw new NotSupportedException();set => throw new NotSupportedException(); } //不能使用Seek方法 public override long Seek(long offset, SeekOrigin origin) {throw new NotSupportedException(); }

相信通過這些我們可以清楚的看到針對HttpRequestStream的設置或者寫相關的操作是不被允許的,這也是為啥我們上面直接通過Seek設置Position的時候為啥會報錯,還有一些其他操作的限制,總之默認是不希望我們對HttpRequestStream做過多的操作,特別是設置或者寫相關的操作。但是我們使用EnableBuffering的時候卻沒有這些問題,究竟是為什么?接下來我們要揭開它的什么面紗了。首先我們從Request.EnableBuffering()這個方法入手,找到源碼位置在HttpRequestRewindExtensions擴展類中[點擊查看源碼????[13]],我們從最簡單的無參方法開始看到如下定義

/// <summary> /// 確保Request.Body可以被多次讀取 /// </summary> /// <param name="request"></param> public static void EnableBuffering(this HttpRequest request) {BufferingHelper.EnableRewind(request); }

上面的方法是最簡單的形式,還有一個EnableBuffering的擴展方法是參數(shù)最全的擴展方法,這個方法可以控制讀取的大小和控制是否存儲到磁盤的限定大小

/// <summary> /// 確保Request.Body可以被多次讀取 /// </summary> /// <param name="request"></param> /// <param name="bufferThreshold">內(nèi)存中用于緩沖流的最大大小(字節(jié))。較大的請求主體被寫入磁盤。</param> /// <param name="bufferLimit">請求正文的最大大小(字節(jié))。嘗試讀取超過此限制將導致異常</param> public static void EnableBuffering(this HttpRequest request, int bufferThreshold, long bufferLimit) {BufferingHelper.EnableRewind(request, bufferThreshold, bufferLimit); }

無論那種形式,最終都是在調(diào)用BufferingHelper.EnableRewind這個方法,話不多說直接找到BufferingHelper這個類,找到類的位置[點擊查看源碼????[14]]代碼不多而且比較簡潔,咱們就把EnableRewind的實現(xiàn)粘貼出來

//默認內(nèi)存中可緩存的大小為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 Bodyvar body = request.Body;//默認情況Body是HttpRequestStream這個類CanSeek是false所以肯定會執(zhí)行到if邏輯里面if (!body.CanSeek){//實例化了FileBufferingReadStream這個類,看來這是關鍵所在var fileStream = new FileBufferingReadStream(body, bufferThreshold,bufferLimit,AspNetCoreTempDirectory.TempDirectoryFactory);//賦值給Body,也就是說開啟了EnableBuffering之后Request.Body類型將會是FileBufferingReadStreamrequest.Body = fileStream;//這里要把fileStream注冊給Response便于釋放request.HttpContext.Response.RegisterForDispose(fileStream);}return request; }

從上面這段源碼實現(xiàn)中我們可以大致得到兩個結論

?BufferingHelper的EnableRewind方法也是HttpRequest的擴展方法,可以直接通過Request.EnableRewind的形式調(diào)用,效果等同于調(diào)用Request.EnableBuffering因為EnableBuffering也是調(diào)用的EnableRewind?啟用了EnableBuffering這個操作之后實際上會使用FileBufferingReadStream替換掉默認的HttpRequestStream,所以后續(xù)處理RequestBody的操作將會是FileBufferingReadStream實例

通過上面的分析我們也清楚的看到了,核心操作在于FileBufferingReadStream這個類,而且從名字也能看出來它肯定是也繼承了Stream抽象類,那還等啥直接找到FileBufferingReadStream的實現(xiàn)[點擊查看源碼????[15]],首先來看他類的定義

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//出現(xiàn)沒讀取完成但是原始位置信息和當前位置信息不一致則直接拋出異常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的Seekreturn _buffer.Seek(offset, origin); }

因為重寫了一些關鍵設置,所以我們可以設置一些流相關的操作。從Seek方法中我們看到了兩個比較重要的參數(shù)_completelyBuffered和_buffer,_completelyBuffered用來判斷原始的HttpRequestStream是否讀取完成,因為FileBufferingReadStream歸根結底還是先讀取了HttpRequestStream的內(nèi)容。_buffer正是承載從HttpRequestStream讀取的內(nèi)容,我們大致抽離一下邏輯看一下,切記這不是全部邏輯,是抽離出來的大致思想

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方法位置[點擊查看源碼????[16]]

private readonly Stream _inner; public FileBufferingReadStream(Stream inner) {//接收原始的Request.Body_inner = inner; } public override int Read(Span<byte> buffer) {ThrowIfDisposed();//如果讀取完成過則直接在buffer中獲取信息直接返回if (_buffer.Position < _buffer.Length || _completelyBuffered){return _buffer.Read(buffer);}//未讀取完成才會走到這里//_inner正是接收的原始的RequestBody//讀取的RequestBody放入buffer中var read = _inner.Read(buffer);//超過設定的長度則會拋出異常if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length){throw new IOException("Buffer limit exceeded.");}//如果設定存儲在內(nèi)存中并且Body長度大于設定的可存儲在內(nèi)存中的長度,則存儲到磁盤中if (_inMemory && _memoryThreshold - read < _buffer.Length){_inMemory = false;//緩存原始的Body流var oldBuffer = _buffer;//創(chuàng)建緩存文件_buffer = CreateTempFile();//超過內(nèi)存存儲限制,但是還未寫入過臨時文件if (_rentedBuffer == null){oldBuffer.Position = 0;var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));try{//將Body流讀取到緩存文件流中var copyRead = oldBuffer.Read(rentedBuffer);//判斷是否讀取到結尾while (copyRead > 0){//將oldBuffer寫入到緩存文件流_buffer當中_buffer.Write(rentedBuffer.AsSpan(0, copyRead));copyRead = oldBuffer.Read(rentedBuffer);}}finally{//讀取完成之后歸還臨時緩沖區(qū)到ArrayPool中_bytePool.Return(rentedBuffer);}}else{_buffer.Write(_rentedBuffer.AsSpan(0, (int)oldBuffer.Length));_bytePool.Return(_rentedBuffer);_rentedBuffer = null;}}//如果讀取RequestBody未到結尾,則一直寫入到緩存區(qū)if (read > 0){_buffer.Write(buffer.Slice(0, read));}else{//如果已經(jīng)讀取RequestBody完畢,也就是寫入到緩存完畢則更新_completelyBuffered//標記為以全部讀取RequestBody完成,后續(xù)在讀取RequestBody則直接在_buffer中讀取_completelyBuffered = true;}//返回讀取的byte個數(shù)用于外部StreamReader判斷讀取是否完成return read; }

代碼比較多看著也比較復雜,其實核心思路還是比較清晰的,我們來大致的總結一下

?首先判斷是否完全的讀取過原始的RequestBody,如果完全完整的讀取過RequestBody則直接在緩沖區(qū)中獲取返回?如果RequestBody長度大于設定的內(nèi)存存儲限定,則將緩沖寫入磁盤臨時文件中?如果是首次讀取或為完全完整的讀取完成RequestBody,那么將RequestBody的內(nèi)容寫入到緩沖區(qū),知道讀取完成

其中CreateTempFile這是創(chuàng)建臨時文件的操作流,目的是為了將RequestBody的信息寫入到臨時文件中。可以指定臨時文件的地址,若如果不指定則使用系統(tǒng)默認目錄,它的實現(xiàn)如下[點擊查看源碼????[17]]

private Stream CreateTempFile() {//判斷是否制定過緩存目錄,沒有的話則使用系統(tǒng)臨時文件目錄if (_tempFileDirectory == null){Debug.Assert(_tempFileDirectoryAccessor != null);_tempFileDirectory = _tempFileDirectoryAccessor();Debug.Assert(_tempFileDirectory != null);}//臨時文件的完整路徑_tempFileName = Path.Combine(_tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid().ToString() + ".tmp");//返回臨時文件的操作流return new FileStream(_tempFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, 1024 * 16,FileOptions.Asynchronous | FileOptions.DeleteOnClose | FileOptions.SequentialScan); }

我們上面分析了FileBufferingReadStream的Read方法這個方法是同步讀取的方法可供StreamReader的ReadToEnd方法使用,當然它還存在一個異步讀取方法ReadAsync供StreamReader的ReadToEndAsync方法使用。這兩個方法的實現(xiàn)邏輯是完全一致的,只是讀取和寫入操作都是異步的操作,這里咱們就不介紹那個方法了,有興趣的同學可以自行了解一下ReadAsync方法的實現(xiàn)[點擊查看源碼????[18]]

當開啟EnableBuffering的時候,無論首次讀取是設置了AllowSynchronousIO為true的ReadToEnd同步讀取方式,還是直接使用ReadToEndAsync的異步讀取方式,那么再次使用ReadToEnd同步方式去讀取Request.Body也便無需去設置AllowSynchronousIO為true。因為默認的Request.Body已經(jīng)由HttpRequestStream實例替換為FileBufferingReadStream實例,而FileBufferingReadStream重寫了Read和ReadAsync方法,并不存在不允許同步讀取的限制。

總結

????本篇文章篇幅比較多,如果你想深入的研究相關邏輯,希望本文能給你帶來一些閱讀源碼的指導。為了防止大家深入文章當中而忘記了具體的流程邏輯,在這里我們就大致的總結一下關于正確讀取RequestBody的全部結論

?首先關于同步讀取Request.Body由于默認的RequestBody的實現(xiàn)是HttpRequestStream,但是HttpRequestStream在重寫Read方法的時候會判斷是否開啟AllowSynchronousIO,如果未開啟則直接拋出異常。但是HttpRequestStream的ReadAsync方法并無這種限制,所以使用異步方式的讀取RequestBody并無異常。?雖然通過設置AllowSynchronousIO或使用ReadAsync的方式我們可以讀取RequestBody,但是RequestBody無法重復讀取,這是因為HttpRequestStream的Position和Seek都是不允許進行修改操作的,設置了會直接拋出異常。為了可以重復讀取,我們引入了Request的擴展方法EnableBuffering通過這個方法我們可以重置讀取位置來實現(xiàn)RequestBody的重復讀取。?關于開啟EnableBuffering方法每次請求設置一次即可,即在準備讀取RequestBody之前設置。其本質(zhì)其實是使用FileBufferingReadStream代替默認RequestBody的默認類型HttpRequestStream,這樣我們在一次Http請求中操作Body的時候其實是操作FileBufferingReadStream,這個類重寫Stream的時候Position和Seek都是可以設置的,這樣我們就實現(xiàn)了重復讀取。?FileBufferingReadStream帶給我們的不僅僅是可重復讀取,還增加了對RequestBody的緩存功能,使得我們在一次請求中重復讀取RequestBody的時候可以在Buffer里直接獲取緩存內(nèi)容而Buffer本身是一個MemoryStream。當然我們也可以自己實現(xiàn)一套邏輯來替換Body,只要我們重寫的時候讓這個Stream支持重置讀取位置即可。

以上就是本次筆者對關于如何更好的方式操作Request.Body的理解,關于講解內(nèi)容筆者深知自己能力有限,理解的不一定透徹,甚至理解的不一定對,還望大家多多諒解,也歡迎大家能夠多多交流。

References

[1]?點擊查看源碼????:?https://github.com/dotnet/runtime/blob/v5.0.5/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs#L393
[2]?點擊查看源碼????:?https://github.com/dotnet/runtime/blob/v5.0.5/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs#L576
[3]?點擊查看源碼????:?https://github.com/dotnet/runtime/blob/v5.0.5/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs#L898
[4]?點擊查看源碼????:?https://github.com/dotnet/runtime/blob/v5.0.5/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs#L1222
[5]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs
[6]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs#L55
[7]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Infrastructure/BodyControl.cs
[8]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs#L315
[9]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs
[10]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs#L82
[11]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs#L92
[12]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs
[13]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs
[14]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Http/Http/src/Internal/BufferingHelper.cs
[15]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Http/WebUtilities/src/FileBufferingReadStream.cs
[16]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Http/WebUtilities/src/FileBufferingReadStream.cs#L212
[17]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Http/WebUtilities/src/FileBufferingReadStream.cs#L198
[18]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.5/src/Http/WebUtilities/src/FileBufferingReadStream.cs#L285

????歡迎掃碼關注我的公眾號????

總結

以上是生活随笔為你收集整理的深入探究ASP.NET Core读取Request.Body的正确方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

青青啪 | 国产成人精品久久久 | 亚州精品一二三区 | 欧美日韩色婷婷 | 久久av一区二区三区亚洲 | 99精品国产亚洲 | 国产视频 亚洲视频 | 国产午夜精品一区二区三区嫩草 | 天天综合网入口 | 中文在线√天堂 | 欧美一级电影 | 日韩免费一区二区 | 久久久久国产成人精品亚洲午夜 | 91资源在线免费观看 | 激情欧美一区二区免费视频 | 国产丝袜| 国产啊v在线观看 | 久久99偷拍视频 | 久久久久免费精品视频 | 久青草国产在线 | 日韩在线视频在线观看 | 久久99国产精品免费网站 | 国产成人亚洲精品自产在线 | 欧美在线视频精品 | 国产黄色精品在线观看 | 亚州精品在线视频 | 狠狠色丁香久久婷婷综合丁香 | 一区二区三区日韩在线观看 | 欧美性色黄 | 在线小视频 | 91在线91拍拍在线91 | 国产又粗又硬又爽视频 | 日韩国产精品一区 | 99视频在线观看免费 | 久草在线视频免费资源观看 | 成年人黄色av | 国产精品 日韩精品 | 日av免费| 亚洲精品乱码久久 | 国产在线免费av | 久久久伊人网 | 激情综合色播五月 | 五月综合色 | 激情小说网站亚洲综合网 | 精品欧美小视频在线观看 | 国产v欧美 | 99久久夜色精品国产亚洲96 | 999久久久久| 日韩精品不卡在线观看 | 久久国产一区二区三区 | 中文字幕av影院 | 久久99久国产精品黄毛片入口 | 极品久久久 | 欧美一区日韩一区 | 欧美热久久 | 久色婷婷 | 黄色一级在线视频 | 久久精品国产第一区二区三区 | 九九九在线观看视频 | 西西4444www大胆视频 | 99九九免费视频 | 深爱开心激情网 | 久久久精品免费看 | 狠狠狠狠狠狠干 | 久久成人资源 | 中文国产在线观看 | 国产精品一二 | 日韩精品一区电影 | 天天干视频在线 | av中文字幕网址 | 久久黄色美女 | 91免费看黄色| 国产专区精品视频 | 在线国产一区二区三区 | 狠狠色丁婷婷日日 | 日日夜夜艹| 中午字幕在线观看 | av在线电影播放 | 中文字幕九九 | 99人久久精品视频最新地址 | 免费男女网站 | 最近免费中文字幕大全高清10 | 国产成人99久久亚洲综合精品 | 亚洲最大av在线播放 | 国产一区在线观看免费 | 国产精品高清免费在线观看 | 国产午夜精品一区二区三区欧美 | 亚洲视频大全 | av资源免费观看 | 亚洲精品视频网站在线观看 | 中文字幕在线观看完整版 | 国产精品一区二区av | 午夜视频在线观看欧美 | 精品免费久久 | 亚洲激情视频在线观看 | 免费福利视频导航 | 97超级碰碰碰碰久久久久 | 精品国产一区二区三区四区在线观看 | 国产小视频福利在线 | 欧美日韩综合在线 | 欧美日韩一区二区三区在线免费观看 | 久草在线资源免费 | 日韩精品一区二区免费视频 | 日本久久久久久久久久 | 久久精品国产99国产 | zzijzzij亚洲日本少妇熟睡 | 久久黄色影院 | 在线观看亚洲国产 | 在线视频欧美亚洲 | 国产免费成人av | 在线性视频日韩欧美 | 久久伊人爱 | 黄色精品一区二区 | 国产精品a级| 蜜臀av麻豆 | 亚洲免费在线视频 | 日p在线观看| 超碰97在线看 | 国产高清精 | 国产成人一区在线 | 国产一区二区手机在线观看 | 精品成人久久 | 在线免费观看的av网站 | www.亚洲黄色 | 久久人人爽人人爽人人片 | 国产精品美女久久久久久久网站 | 99综合影院在线 | 中国一级片免费看 | 亚洲第一区在线观看 | 91在线视频| 精品一区二区日韩 | 久久国产免费视频 | 国产一二三四在线视频 | 91av电影网 | 亚洲欧洲美洲av | 免费a v在线 | a天堂中文在线 | 日日夜夜草 | 成人在线电影观看 | 92中文资源在线 | 久久精品一区二区三 | 亚洲国产影院av久久久久 | 亚洲视频,欧洲视频 | 欧美激情精品久久久久久变态 | 中文字幕在线色 | 91久久久国产精品 | 日韩精品久久一区二区三区 | 国产伦精品一区二区三区无广告 | 久久精品黄色 | 免费看的黄色 | 午夜视频在线观看一区二区 | 久久艹人人 | 国产福利中文字幕 | 国产系列在线观看 | 欧美a影视 | 国产一卡久久电影永久 | 亚洲黄色av网址 | 久久午夜国产精品 | 丁香伊人网 | 久久黄视频 | 国产高清视频 | 中文字幕频道 | 黄色一二级片 | 三级黄色免费片 | 久久影院精品 | 欧美国产亚洲精品久久久8v | 98超碰在线 | 久久综合免费 | 丁香av| 国内精品免费久久影院 | 91视频在线免费 | aaawww| 日韩性xxxx | 亚洲第二色 | 色综合中文综合网 | 国产中文字幕视频在线 | 国产黄色一级大片 | 欧美淫aaa免费观看 日韩激情免费视频 | 99精品在线免费视频 | 久久精品福利视频 | 色欲综合视频天天天 | 国产精品久久久久久久久久ktv | 日韩高清av| 黄色三级免费看 | 免费视频三区 | 丁香高清视频在线看看 | 热久久这里只有精品 | 一区二区三区四区在线免费观看 | 欧美性生爱 | 在线黄频| 国内精品久久久久久久 | 免费观看黄色av | 国产麻豆剧传媒免费观看 | 久久久久久久av麻豆果冻 | 黄色大全视频 | 久艹在线免费观看 | 免费中午字幕无吗 | 午夜视频免费 | 日韩在线观看小视频 | 国产中文字幕大全 | 一区二区三区中文字幕在线观看 | 亚洲精品乱码久久久久久蜜桃不爽 | 久草在线免费看视频 | www.五月天激情 | 天天干,天天干 | 香蕉影院在线观看 | 菠萝菠萝蜜在线播放 | 国内久久精品视频 | 成 人 a v天堂 | 久久蜜臀一区二区三区av | 成人黄色免费在线观看 | 亚洲精品av在线 | 五月天婷婷狠狠 | www.av在线.com | 美女免费黄视频网站 | 中文字幕av在线不卡 | 成人影音av | 久久精品视频18 | 欧美一级电影在线观看 | 国产91综合一区在线观看 | 91九色在线视频观看 | av三级在线看 | 国产精品不卡一区 | 久久久精品在线观看 | 国产又黄又爽无遮挡 | 欧美网址在线观看 | 久久资源总站 | 国产成人免费观看 | 日韩黄色大片在线观看 | 狠狠五月婷婷 | 中文字幕日韩有码 | 在线三级av | 日本黄色免费播放 | 丁香av在线| 国产精品麻豆免费版 | 中文字幕精品一区久久久久 | 久久激情日本aⅴ | 九九九国产 | av7777777| 日韩色在线观看 | 欧美日韩中文在线视频 | 久久久久亚洲精品男人的天堂 | 人人爱爱人人 | 久久视频网址 | 亚洲精品88欧美一区二区 | 美女黄久久| 日韩在线观看视频一区二区三区 | 五月激情婷婷丁香 | www.五月天婷婷| 日韩视频一区二区在线 | avove黑丝| 麻豆传媒在线视频 | 999男人的天堂 | 亚洲国产精品99久久久久久久久 | 日韩高清av在线 | 亚洲欧美婷婷六月色综合 | 一级久久精品 | 色在线网| 久久视频国产精品免费视频在线 | 国产精品免费在线播放 | 亚洲国内精品在线 | 精品国产一区二区三区久久久蜜臀 | 成人动漫视频在线 | 狠狠色狠狠综合久久 | 国产v欧美| 欧美韩国日本在线 | 91亚洲精品国偷拍 | 国产999精品视频 | 国产va饥渴难耐女保洁员在线观看 | 日韩视频a| 日本久久久亚洲精品 | 精品免费观看 | 精品一区二区精品 | 中文字幕影片免费在线观看 | 亚洲成a人片在线www | 97视频在线观看免费 | 国产色秀视频 | 国语对白少妇爽91 | 欧美一级裸体视频 | 久久久伦理 | 久久99亚洲精品久久 | 九九九九九九精品任你躁 | 日本精品一区二区 | 天天想夜夜操 | 中文字幕一区二区三区四区 | 国产成人99久久亚洲综合精品 | 人人干人人模 | 欧美视频国产视频 | 午夜91在线 | 天天操人人要 | 精品在线亚洲视频 | 午夜日b视频 | 福利视频一区二区 | 久久尤物电影视频在线观看 | 国产精品久久久久久一区二区三区 | 久久精品国产亚洲 | 中文av不卡 | 超碰夜夜 | 日本中文字幕在线观看 | 国产成人精品999在线观看 | 超碰人人射 | 久久精彩免费视频 | 菠萝菠萝在线精品视频 | 97超碰在线久草超碰在线观看 | 婷婷伊人五月 | 亚洲久草在线视频 | 免费观看一级一片 | 女人18片 | 丁香五月网久久综合 | 国产精品久久久久久高潮 | 久久国内免费视频 | 久久久久久久久久久综合 | 久久国产精品二国产精品中国洋人 | 在线之家免费在线观看电影 | 欧美久久综合 | 国产精品一区二区在线观看免费 | 欧美日韩精品网站 | 日韩在线视频一区 | 日韩高清毛片 | 日日婷婷夜日日天干 | 日韩系列在线观看 | 不卡的av在线 | 人人草在线视频 | 看毛片的网址 | 黄色网在线免费观看 | av在线播放一区二区三区 | 成人一级片在线观看 | 久久人人爽人人爽人人片av软件 | 99精品视频在线播放免费 | 日韩av伦理片 | 日韩综合视频在线观看 | 久久综合九九 | 毛片基地黄久久久久久天堂 | 日韩黄在线观看 | 又黄又刺激视频 | 波多野结衣最新 | 亚洲a成人v| 国产精品免费不 | 精品一区二区日韩 | 国产免费高清 | 国产精品亚洲成人 | 久久午夜免费视频 | 五月婷丁香| 99精品在线免费 | 久久亚洲欧美 | 亚洲欧美视频在线观看 | www.超碰97.com| 狠狠色狠狠色终合网 | 久久国产精品免费一区 | 欧美激情第八页 | 日本特黄特色aaa大片免费 | 日韩两性视频 | 免费亚洲片 | 亚州视频在线 | 天天干夜夜爽 | 亚洲欧洲中文日韩久久av乱码 | 手机在线欧美 | 天天鲁天天干天天射 | 六月丁香色婷婷 | 毛片在线网| 在线观看中文字幕第一页 | 探花视频在线观看免费版 | 久香蕉 | 91热在线 | 色婷婷狠狠操 | 日本在线精品视频 | 国产一区二区在线播放视频 | 中文字幕亚洲精品日韩 | 婷婷久月 | 亚洲午夜av久久乱码 | 国产精品一区二区电影 | 久久国产精品一国产精品 | 国产三级av在线 | 在线国产日本 | 欧美日韩国产一区 | 成人av高清| 国产中文字幕第一页 | 欧洲亚洲国产视频 | 一区精品久久 | 黄色av电影免费观看 | 免费视频久久 | 18国产精品白浆在线观看免费 | 日韩精品一区二区不卡 | 国产一区二区影院 | 国产免费一区二区三区最新6 | 一 级 黄 色 片免费看的 | 91av视频免费观看 | 美女网站色在线观看 | 国产精品视频专区 | 亚洲va欧美va人人爽 | 久久综合色婷婷 | 亚洲精品99久久久久中文字幕 | 欧美一级乱黄 | 国产亚洲精品综合一区91 | 国产丝袜一区二区三区 | 国产免费人人看 | 国产91精品看黄网站在线观看动漫 | 久久免费观看视频 | 亚洲精品男人天堂 | 欧美一级性 | 久久久精品欧美一区二区免费 | 日日操操 | 精品99在线视频 | 丁香激情视频 | 亚洲精品欧美精品 | 91c网站色版视频 | 日韩一级电影网站 | 天天插综合 | 亚洲美女久久 | 精品国产乱码久久久久久1区二区 | 天天操网站 | 在线看v片 | 色插综合| 国产福利91精品 | 国产色中涩 | 日韩欧美一区二区不卡 | 黄色一级网 | 国产精品久久久久一区二区三区共 | 日韩精品一区二区三区免费观看 | 97夜夜澡人人爽人人免费 | 午夜影院先 | 亚洲激精日韩激精欧美精品 | 99爱在线观看 | 欧美孕交vivoestv另类 | 国产免费观看高清完整版 | 久久se视频 | 日本精品久久久久久 | 色播激情五月 | 六月丁香激情综合色啪小说 | 日韩mv欧美mv国产精品 | 日日爽天天爽 | 天天玩天天干天天操 | 99视频一区二区 | 黄色a视频| 久久精品永久免费 | 成年人在线免费看视频 | 精品在线观 | 91亚洲精品久久久中文字幕 | 日韩电影在线观看一区二区三区 | 欧美日韩视频在线一区 | 成人a在线观看 | 国产女人40精品一区毛片视频 | 国产黄色大片 | 又黄又爽的视频在线观看网站 | 一区二区三区在线视频观看58 | 亚洲国产综合在线 | 91精品视频网站 | 国产大片免费久久 | 99精品欧美一区二区三区 | 在线视频 一区二区 | 2023亚洲精品国偷拍自产在线 | 高清av免费看 | 亚洲激情精品 | www.天堂av| 婷婷国产一区二区三区 | 亚洲天天摸日日摸天天欢 | 1024手机在线看 | 色老板在线视频 | 7777精品伊人久久久大香线蕉 | 视频成人永久免费视频 | 日韩一区正在播放 | 超碰av在线 | 国产美女网站视频 | 日韩av综合网站 | 中文字幕在线免费观看 | 国产精品涩涩屋www在线观看 | 色一级片| 狠狠色噜噜狠狠狠狠 | 天天爱天天舔 | 国产精品久久久久影院日本 | 天天干天天上 | 日本三级不卡视频 | 日韩欧美视频一区 | 国产精品一区二区在线观看免费 | 国产精品九九久久久久久久 | 粉嫩av一区二区三区入口 | 婷婷伊人五月天 | 91大神电影 | 久久久久观看 | 亚洲一区美女视频在线观看免费 | 国产欧美精品一区二区三区四区 | 天天插天天狠 | 国产精品成人久久久久久久 | 日韩欧美在线国产 | av在线等| 日韩欧美视频免费在线观看 | 日韩在线观看第一页 | 91在线网址 | 欧美成人一二区 | 亚洲精品乱码久久久久久蜜桃不爽 | 中文字幕在线免费看线人 | 亚洲国产精品传媒在线观看 | 久久久久久久国产精品 | 四虎在线免费 | 激情婷婷在线 | 色a在线观看 | 999精品 | 日韩免费一区二区三区 | 美女黄频在线观看 | 日日操天天操狠狠操 | 91探花在线视频 | 国产精品免费不卡 | 超薄丝袜一二三区 | 在线观看中文字幕一区 | 2000xxx影视 | 美女视频黄,久久 | a级一a一级在线观看 | 91大神在线看 | 91亚洲精品乱码久久久久久蜜桃 | 中文字幕丝袜制服 | 国产精品久免费的黄网站 | 夜色资源网 | 天天综合久久综合 | 91av在线免费看 | 精品国产一区二区三区四区在线观看 | 国产一级在线免费观看 | 91视频免费网站 | 久久久久久久久国产 | 99免费在线视频观看 | 国产亚洲va综合人人澡精品 | 成人一区电影 | 久久不射影院 | 搡bbbb搡bbb视频 | 亚洲成av人片在线观看香蕉 | 日韩免费中文字幕 | www.av免费 | 久爱精品在线 | a级免费观看 | 日韩二区三区在线 | 成人国产亚洲 | 在线成人看片 | 国产精品18久久久久久久久久久久 | 久久国产午夜精品理论片最新版本 | 99成人免费视频 | 国产成人精品在线观看 | 亚洲精品国偷自产在线91正片 | 日韩视频免费观看高清完整版在线 | 日韩黄色在线 | www免费视频com| 亚洲永久国产精品 | 日韩大陆欧美高清视频区 | 99久在线精品99re8热视频 | 五月开心婷婷网 | 久久亚洲影视 | 在线亚洲人成电影网站色www | 天天操,夜夜操 | 中国一区二区视频 | 五月婷婷深开心 | 亚洲精品免费在线观看视频 | 久久久久久网站 | 国产美女主播精品一区二区三区 | 久久99国产综合精品免费 | 国产免费叼嘿网站免费 | 亚洲丁香日韩 | 午夜精品视频一区 | 久久久男人的天堂 | 99热99热 | 在线观看完整版免费 | 精品爱爱 | 色在线免费观看 | avsex| 狠狠狠狠狠色综合 | 精品久久久久久国产偷窥 | 国产成人精品一区二区三区福利 | 中文字幕视频网站 | 欧美美女激情18p | 夜夜躁狠狠躁日日躁 | 在线中文日韩 | 国产中的精品av小宝探花 | 91精品在线免费观看 | 欧美 日韩 国产 成人 在线 | 狂野欧美激情性xxxx欧美 | www欧美xxxx | 久草免费色站 | 日日干美女 | 国产精品视屏 | 91免费高清在线观看 | 免费一级特黄录像 | 精品在线亚洲视频 | 亚洲视频2 | 91精品1区2区 | 国产91精品在线播放 | 五月婷婷操 | 日韩精品影视 | 亚洲片在线| 91麻豆产精品久久久久久 | 在线a人片免费观看视频 | 午夜狠狠操| 天天艹天天爽 | 国产午夜精品一区二区三区嫩草 | 国产精品久久久久久一二三四五 | 成人av电影免费在线观看 | 最新中文字幕在线观看视频 | 日韩va在线观看 | 插婷婷 | 人人干人人添 | 亚洲少妇天堂 | 蜜臀av网站| 中文字幕免费一区 | 波多野结衣在线观看一区二区三区 | 久久91久久久久麻豆精品 | 久久久激情视频 | 一区二区三区免费在线观看视频 | 国产精品成人一区二区三区吃奶 | 国产精品一区二区三区视频免费 | 天天操天天色天天射 | 丁香久久久 | 中文字幕久久久精品 | 国产不卡在线观看视频 | 91天堂在线观看 | 中文字幕高清免费日韩视频在线 | 久草视频2| 日韩黄色中文字幕 | 99久久99久国产黄毛片 | 久久99爱视频 | 成人在线观看资源 | 久久高清视频免费 | 欧美日韩高清在线 | 成人在线观看你懂的 | 99视频在线观看视频 | www.人人草 | 日批网站免费观看 | 国产黄色一级片在线 | 天天干 天天摸 天天操 | av黄色免费看 | 久久亚洲电影 | 天天艹天天干天天 | 中文视频一区二区 | 在线观看中文字幕一区二区 | 国产一卡久久电影永久 | 六月久久婷婷 | 精品国产乱码久久久久久久 | 97在线超碰 | 欧美色综合久久 | 精品久久久久久一区二区里番 | 91天天操 | 久久成年人网站 | 欧美精品在线一区二区 | 中文网丁香综合网 | 色婷婷六月天 | 中文字幕电影在线 | 综合黄色网 | 在线小视频国产 | 丁香五月亚洲综合在线 | 成人精品一区二区三区中文字幕 | 97av视频| 美女性爽视频国产免费app | 免费看污黄网站 | 97色婷婷 | 国产小视频福利在线 | 久久久首页| 日韩免费网址 | 在线免费色视频 | 毛片3 | www.99久久.com| 国产成人一区二区三区在线观看 | 国产亚洲aⅴaaaaaa毛片 | 91九色最新 | 久久伊人八月婷婷综合激情 | 欧美一级日韩三级 | 欧美成人一二区 | 日日夜夜国产 | 亚洲小视频在线观看 | 中文字幕国语官网在线视频 | 亚洲综合爱 | 欧美嫩草影院 | 国产在线观看,日本 | 五月婷在线播放 | 亚洲免费国产视频 | 色天天综合网 | 天天射天天干天天爽 | 久久成人国产精品免费软件 | 91九色蝌蚪国产 | 精品久久久久久久久久久久久久久久久久 | 欧美日韩免费观看一区二区三区 | 特黄一级毛片 | 91中文在线视频 | 国产视频高清 | 久久69精品 | 黄色午夜 | 欧美日韩中字 | 欧美最新大片在线看 | 婷婷午夜天| av电影一区二区三区 | 9999国产精品 | 夜夜狠狠| 日韩久久精品一区二区三区下载 | 久久精品国产一区二区三区 | 深夜激情影院 | 97视频免费在线 | 美女视频a美女大全免费下载蜜臀 | 午夜av免费观看 | 97在线影院 | 欧美日韩免费在线视频 | 婷婷av在线 | 丁香电影小说免费视频观看 | 日韩三级视频在线观看 | 九九久久影院 | 国产一卡久久电影永久 | 亚洲精品免费在线 | 国产精选在线观看 | 九九视频免费在线观看 | 国产91精品一区二区麻豆网站 | 激情五月婷婷综合 | 国产在线色视频 | 美女福利视频在线 | 在线免费观看视频一区二区三区 | 精品国产电影一区 | 欧美一级性生活 | 久草精品电影 | 亚洲精品免费观看视频 | 91精品入口 | 亚洲精品自在在线观看 | 国产视频资源在线观看 | 韩国av电影在线观看 | 99热999 | 超碰久热 | 美州a亚洲一视本频v色道 | 国产日韩欧美在线观看视频 | 91九色蝌蚪视频网站 | 欧美午夜寂寞影院 | 最新日韩电影 | 亚洲天堂色婷婷 | 婷婷精品在线视频 | 日韩欧美电影 | 国产一区私人高清影院 | 欧美国产亚洲精品久久久8v | 九九九热精品 | 久久视频网址 | 亚洲免费av观看 | 一级黄色大片 | 亚洲精品视频网站在线观看 | 人人插人人舔 | 国产视频不卡一区 | 99tvdz@gmail.com | 亚洲精品久久视频 | 亚洲一本视频 | 国产精品美女视频网站 | 五月婷婷中文 | 黄色一级免费电影 | 亚洲精品福利在线观看 | 99久久www| 国产91在 | 成人动漫一区二区三区 | 蜜臀av性久久久久av蜜臀妖精 | 一区 在线观看 | 婷婷av在线 | 久久艹欧美 | 久久精品人 | 久久人人看 | 丁香五月缴情综合网 | 美女视频黄频大全免费 | www.夜色321.com | 亚洲高清av在线 | 国产精品久久久久永久免费看 | 国产精品视频内 | 99999精品 | 亚洲国产精品电影在线观看 | 亚洲色图av | 久久国产区 | 日韩精品视频第一页 | 日韩理论电影在线观看 | 精品毛片在线 | 欧美一区日韩精品 | 国产视频精品免费播放 | 日韩电影一区二区三区 | 久久国产精品系列 | 国产韩国日本高清视频 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 午夜 在线| 在线看片a | 国产精品欧美久久久久久 | 色夜视频 | 日韩高清在线不卡 | 亚洲国产精品久久久久婷婷884 | 亚洲高清资源 | 91超国产| www黄在线| 中文字幕免费成人 | 午夜av在线播放 | 在线v片免费观看视频 | 日本一区二区免费在线观看 | 成年人免费在线观看网站 | 综合精品久久久 | 精品亚洲欧美一区 | 免费高清在线观看成人 | av片子在线观看 | av在线看片 | 亚洲一区美女视频在线观看免费 | 天天艹天天爽 | 国产免费三级在线观看 | 一区二区三区在线电影 | 操操操人人| av电影在线免费观看 | 干干操操 | 99国产成+人+综合+亚洲 欧美 | 99热999| 探花视频在线观看免费版 | 蜜臀av麻豆 | 精品在线视频一区 | 在线视频99 | 中文字幕国产精品一区二区 | 激情视频在线观看网址 | 在线观看自拍 | 精品亚洲一区二区 | 久久99精品久久久久蜜臀 | 国产精品区免费视频 | 伊人永久在线 | 天堂av免费在线 | 久久精品视频网址 | av手机在线播放 | 中文字幕观看在线 | 国产原创av在线 | 国产又粗又猛又黄又爽视频 | 久久久久久久久亚洲精品 | 久草免费在线观看 | 国产视频999 | 激情视频91| 亚洲日本三级 | 久久久久国产一区二区三区 | 国产精品国产精品 | 97超碰色偷偷 | 亚洲第一中文网 | 国产成人无码AⅤ片在线观 日韩av不卡在线 | 人人盈棋牌 | 91av网址| 久久国产精品久久久 | 9ⅰ精品久久久久久久久中文字幕 | 国产精品免费视频一区二区 | 四虎影视精品永久在线观看 | 天天爱天天操天天干 | 久久99深爱久久99精品 | 久久你懂的 | 免费成人黄色 | 国产精品久久久久久久久免费 | 五月天综合激情网 | 免费a网 | 99国产精品久久久久老师 | 色综合夜色一区 | 日韩一区二区三区观看 | 精品视频在线播放 | 精品国产1区 | 免费av网站在线看 | 成人黄色国产 | 国内精品99| 色婷婷午夜 | 毛片基地黄久久久久久天堂 | 日韩国产精品久久久久久亚洲 | 中文字幕黄网 | 在线观看日韩视频 | 91精品在线免费 | 成人av在线网 | 亚洲女裸体 | 欧美精品黑人性xxxx | 麻豆视频在线观看 | 欧美另类重口 | 精品国产乱码久久久久久久 | 日韩视频中文字幕 | 日本在线中文 | 久久久久久久久久久福利 | 日韩免费在线网站 | 日韩欧美在线一区 | 欧美中文字幕第一页 | 亚洲国产精品成人va在线观看 | av在线网站免费观看 | 娇妻呻吟一区二区三区 | 久久久久久久久久久久亚洲 | 精品国内 | 国产高清视频在线播放一区 | 欧美综合国产 | 国产尤物一区二区三区 | 欧美一二三在线 | 九九免费在线看完整版 | 国产成人中文字幕 | 国产一二三四在线观看视频 | 久久香蕉国产 | 在线观看欧美成人 | 亚洲精品国产精品国自产观看 | 中文字幕在线国产精品 | 国产久草在线观看 | 最新的av网站 | 色永久免费视频 | 97激情影院 | 欧美日韩在线精品 | 国产美女网站在线观看 | 特级黄色视频毛片 | 久久免费视频播放 | 日韩在线观看小视频 | 青青草华人在线视频 | 国产 av 日韩| www.com久久 | av大片网站| 麻花天美星空视频 | 中文字幕亚洲在线观看 | 五月天婷婷视频 | 特级a毛片 | 久久国产麻豆 | 久草国产在线 | 国产精品毛片完整版 | 黄色亚洲大片免费在线观看 | 日韩伦理片hd | 国产精品久久久久久欧美 | 成人污视频在线观看 | 热re99久久精品国产66热 | 亚洲精品午夜视频 | 九九九视频在线 | 中文字幕色在线视频 | 国产亚洲精品免费 | 91资源在线播放 | 欧洲一区二区三区精品 | 999在线精品 | 亚洲精品字幕在线观看 | 中文字幕在线免费观看视频 | 4hu视频 | 国产欧美日韩一区 | 91在线播 | 狠狠色丁香婷婷综合欧美 | 日韩欧美在线影院 | 国产精品一区二区62 | 欧美极品xxx | 91亚洲精品久久久 | 久久超碰在线 | 亚洲人成网站精品片在线观看 | 国产精彩视频一区 | 91av免费在线观看 | 国产在线视频一区二区三区 | 久久精品国产免费 | 久草av在线播放 | www.亚洲在线 | 99久久夜色精品国产亚洲 | 久草精品在线 | 亚洲免费在线 | 中文在线8新资源库 | 久操中文字幕在线观看 | 在线韩国电影免费观影完整版 | 97视频资源| 韩日精品在线 | 久久小视频 | 久久夜夜操 | 精品一区电影国产 | 福利视频网站 | 色欧美日韩 | 免费在线观看的av网站 | 天堂av在线 | 欧美日韩免费一区二区 | 久久99国产综合精品免费 | 亚洲三级黄色 | 国产午夜av | 国产女人40精品一区毛片视频 | 久久a久久| 日日干夜夜爱 | 久久超级碰视频 | 精品福利在线视频 | 三级黄色免费 | 成人午夜网址 | 在线视频第一页 | 99精品国产在热久久 | 91福利区一区二区三区 | 成人在线观看影院 | av在线播放一区二区三区 | 日本性xxx| 国产精品九九九九九 | 激情五月综合网 | 亚洲综合激情 | 日韩在线电影 | 国产剧情一区二区在线观看 | 国产精品久久在线观看 | 最新av网站在线观看 | 一区二区激情视频 | 日本九九视频 | 涩涩成人在线 | 日韩久久久久久 | 欧美福利视频 | 黄色av网站在线观看免费 | 99久免费精品视频在线观看 | 婷婷福利影院 | av资源免费在线观看 | www.亚洲激情.com | 日韩黄色中文字幕 | 色老板在线视频 | 91精品久久久久久久99蜜桃 | 日本久久久久久科技有限公司 | 日韩欧美成 | 国精产品999国精产 久久久久 | 国产精品免费在线播放 | 岛国一区在线 | 天天干天天爽 | 精品av在线播放 | 欧美成人91 | 欧美精品一区二区性色 | 特级毛片在线观看 | 亚洲精品1区2区3区 超碰成人网 | 91九色综合 | 黄色大片免费播放 | 中文在线免费看视频 | 国产不卡一 | 中文字幕在线免费 | 黄色性av|