日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

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

asp.net

ASP.NET Core使用HostingStartup增强启动操作

發布時間:2023/12/4 asp.net 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core使用HostingStartup增强启动操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概念

????在ASP.NET Core中我們可以使用一種機制來增強啟動時的操作,它就是HostingStartup。如何叫"增強"操作,相信了解過AOP概念的同學應該都非常的熟悉。我們常說AOP使用了關注點分離的方式,增強了對現有邏輯的操作。而我們今天要說的HostingStartup就是為了"增強"啟動操作,這種"增強"的操作甚至可以對對現有的程序可以做到無改動的操作。例如,外部程序集可通過HostingStartup實現為應用提供配置服務、注冊服務或中間件管道操作等。

使用方式

??? HostingStartup屬性表示要在運行時激活的承載啟動程序集。大致分為兩種情況,一種是自動掃描當前Web程序集中通過HostingStartup指定的類,另一種是手動添加配置hostingstartupassembles指定外部的程序集中通過HostingStartup指定的類。第一種方式相對簡單,但是對Web程序本身有入侵,第二種方式稍微復雜一點點,但是可以做到對現有代碼無入侵操作,接下來我們分別演示這兩種使用方式。

ASP.NET Core中直接定義

首先是在ASP.NET Core程序中直接使用HostingStartup,這種方式比較簡單首先在Web程序中隨便定義一個類,然后實現IHostingStartup接口,最后別忘了在程序集中添加HostingStartupAttribute指定要啟動的類的類型,具體代碼如下所示

using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; //通過HostingStartup指定要啟動的類型 [assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))] namespace HostStartupWeb {public class HostingStartupInWeb : IHostingStartup{public void Configure(IWebHostBuilder builder){//程序啟動時打印依據話,代表執行到了這里Debug.WriteLine("Web程序中HostingStartupInWeb類啟動");//可以添加配置builder.ConfigureAppConfiguration(config => {//模擬添加一個一個內存配置var datas = new List<KeyValuePair<string, string>>{new KeyValuePair<string, string>("ServiceName", "HostStartupWeb")};config.AddInMemoryCollection(datas);});//可以添加ConfigureServicesbuilder.ConfigureServices(services=> {//模擬注冊一個PersonDtoservices.AddScoped(provider=>new PersonDto { Id = 1, Name = "yi念之間", Age = 18 });});//可以添加Configurebuilder.Configure(app => {//模擬添加一個中間件app.Use(async (context, next) =>{await next();});});}} }

僅僅使用上面所示的這些代碼,便可在Web程序啟動的時候去自動執行HostingStartupInWeb的Configure方法,在這里面我們幾乎可以使用所有針對ASP.NET Core程序配置的操作,而且不需要在Web程序中額外添加別的代碼就可以自動調用HostingStartupInWeb的Configure方法。

外部程序集引入

我們之前也說過,上面的方式雖然使用起來相對簡單一點,僅僅是一點,那就是省去了指定啟動程序集的邏輯。但是,上面的方式需要在Web程序中添加,這樣的話還是會修改代碼。而且,可能更多的時候我們是在外部的程序集中編寫HostingStartup邏輯,這時候就需要使用另一種方式在將外部程序集中引入HostingStartup。首先我們要在自定義的程序集中至少引入Microsoft.AspNetCore.Hosting包才能使用HostingStartup

<PackageReference?Include="Microsoft.AspNetCore.Hosting"?Version="2.2.7"?/>

如果你不需要使用注冊中間件的邏輯那么僅僅引入Microsoft.AspNetCore.Hosting.Abstractions即可

<PackageReference?Include="Microsoft.AspNetCore.Hosting.Abstractions"?Version="2.2.0"?/>

如果需要使用其他功能包,可以自行在定義的程序集中引入。比如我們定義了一個名為HostStartupLib的Standard類庫,并創建了名為HostStartupLib的類

using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; [assembly: HostingStartup(typeof(HostStartupLib.HostingStartupInLib))] namespace HostStartupLib {public class HostingStartupInLib : IHostingStartup{public void Configure(IWebHostBuilder builder){Debug.WriteLine("Lib程序中HostingStartupInLib類啟動");//添加配置builder.ConfigureAppConfiguration((context, config) => {var datas = new List<KeyValuePair<string, string>>{new KeyValuePair<string, string>("ServiceName", "HostStartupLib")};config.AddInMemoryCollection(datas);});//添加ConfigureServicesbuilder.ConfigureServices(services=> {services.AddScoped(provider=>new PersonDto { Id = 2, Name = "er念之間", Age = 19 });});//添加Configurebuilder.Configure(app => {app.Use(async (context, next) =>{await next();});});}} }

然后我們將自定義的HostStartupLib這個Standard類庫引入Web項目中,運行Web程序,發現HostingStartupInLib的Configure方法并不能被調用。其實我們上面說過了,將HostingStartup從外部程序集引入的話需要手動指定啟動程序集的名稱。指定啟動程序集的方式有兩種,一種是指定IWebHostBuilder的擴展UseSetting指定

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{//通過UseSetting的方式指定程序集的名稱webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib");//如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib;HostStartupLib2");webBuilder.UseStartup<Startup>();});

另一種通過添加環境變量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES的方式,可以通過設置launchSettings.json中

"environmentVariables": {"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib"//如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib;HostStartupLib2" }

可以引入多個包含HostingStartup的程序集,在設置WebHostDefaults.HostingStartupAssembliesKey或者ASPNETCORE_HOSTINGSTARTUPASSEMBLIES指定多個程序集名稱可以使用英文分號(;)隔開程序集名稱。雖然是兩種形似指定,但是其實本質是一樣的那就是設置配置key為hostingStartupAssemblie配置的值,下面我們會詳細講解。
????通過在程序中設置環境變量的方式等同于Window系統中Set的方式設置環境變量,或Linux系統中export的方式設置環境變量,亦或是直接設置系統環境變量,效果都是一致的。指定完成啟動程序集之后,再次運行程序便可以看到HostingStartupInLib的Configure方法被調用到了。在這里我們可以看到如果是使用的環境變量的方式去指定啟動程序集的話,對現有代碼可以做到完全無入侵。

源碼探究

在上面我們簡單的介紹了HostingStartup的概念及基本的使用方式,基于這些我們產生了幾個疑問

  • 首先是關于HostingStartup的基本工作方式是什么

  • 其次是為什么HostingStartup在Web程序中不需要配置程序集信息就可以被調用到,而通過外部程序集引入HostingStartup需要手動指定程序集

  • 最后是通過外部程序集引入HostingStartup的指定方式為何只能是UseSetting和環境變量的方式
    基于以上幾個疑問,我們來探索一下HostingStartup的相關源碼,來揭開它的神秘面紗。首先廢話不多說直接找到源碼位置[點擊查看源碼????]在GenericWebHostBuilder類中的ExecuteHostingStartups方法中,關于GenericWebHostBuilder類我們在上篇文章深入探究ASP.NET Core Startup初始化中主要就是分析這個類,因為這是構建WebHost的默認類,而我們接下來要說的ExecuteHostingStartups方法也是承載在這個類中,直接貼代碼如下所示

private void ExecuteHostingStartups() {//通過配置_config和當前程序集名稱構建WebHostOptions類var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);//如果PreventHostingStartup屬性為true則直接返回//通過這個可以配置阻止啟動邏輯if (webHostOptions.PreventHostingStartup){return;}var exceptions = new List<Exception>();//構建HostingStartupWebHostBuilder_hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this);//GetFinalHostingStartupAssemblies獲取最終要執行的程序集名稱foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)){try{//通過程序集名稱加載程序集信息,因為使用了AssemblyName所以只需要使用程序集名稱即可var assembly = Assembly.Load(new AssemblyName(assemblyName));//獲取包含HostingStartupAttribute的程序集foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>()){//實例化HostingStartupAttribute的HostingStartupType屬性的對象實例//即我們上面聲明的[assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))]var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);//調用HostingStartup的Configure方法hostingStartup.Configure(_hostingStartupWebHostBuilder);}}catch (Exception ex){exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));}}if (exceptions.Count > 0){_hostingStartupErrors = new AggregateException(exceptions);} }

通過上面的源碼我們就可以很清楚的了解到HostingStartup的基本工作方式。獲取的程序集中包含的HostingStartupAttribute,通過獲取HostingStartupAttribute的HostingStartupType屬性得到要執行的IHostingStartup實例,最后執行Configure方法,Configure方法需要傳遞IWebHostBuilder的實例,而HostingStartupWebHostBuilder正是實現了IWebHostBuilder接口。
????我們了解到了HostStartup的工作方式,接下來我們來探究一下為什么HostingStartup在Web程序中不需要配置程序集信息就可以被調用到,而通過外部程序集引入HostingStartup需要手動指定程序集。通過上面的源碼我們可以得到一個信息那就是所有需要啟動的程序集信息都是來自WebHostOptions的GetFinalHostingStartupAssemblies方法,接下來我們就來查看一下GetFinalHostingStartupAssemblies方法的實現源碼[點擊查看源碼????]

public IEnumerable<string> GetFinalHostingStartupAssemblies() {return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase); }

從這里我們可以看出程序集信息來自于HostingStartupAssemblies屬性,而且還要排除掉HostingStartupExcludeAssemblies包含的程序集。我們找到他們初始化的相關邏輯大致如下

//承載啟動是需要調用的HostingStartup程序集 public IReadOnlyList<string> HostingStartupAssemblies { get; set; } //承載啟動時排除掉不不要執行的程序集 public IReadOnlyList<string> HostingStartupExcludeAssemblies { get; set; } //是否阻止HostingStartup啟動執行功能,如果設置為false則HostingStartup功能失效 //通過上面的ExecuteHostingStartups方法源碼可知 public bool PreventHostingStartup { get; set; } //應用程序名稱 public string ApplicationName { get; set; }public WebHostOptions(IConfiguration configuration, string applicationNameFallback) {ApplicationName = configuration[WebHostDefaults.ApplicationKey] ?? applicationNameFallback;HostingStartupAssemblies = Split($"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}");HostingStartupExcludeAssemblies = Split(configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]);PreventHostingStartup = WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey); }//分隔配置的程序集信息,分隔依據為";"分號,這也是我們上面說過配置多程序集的時候采用分號分隔的原因 private IReadOnlyList<string> Split(string value) {return value?.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)?? Array.Empty<string>(); }

首先,通過HostingStartupAssemblies的初始化邏輯我們可以得出,默認會是有兩個數據來源,一個是當前的ApplicationName,另一個是通過HostingStartupAssembliesKey配置的程序集信息。這也解答了我們上面說過的為什么HostingStartup在Web程序中不需要配置程序集信息就可以被調用到,而通過外部程序集引入HostingStartup需要手動指定程序集。其次,我們可以了解到通過配置HostingStartupExcludeAssemblies信息排除你不想啟動的HostingStartup程序集,而且還可以通過配置PreventHostingStartup值來禁止使用HostingStartup的功能。
通過上面的代碼我們還了解到這三個屬性的來源的配置名稱都是來自WebHostDefaults這個常量類,接下來我們查看一下這三個屬性對應的配置名稱

public static readonly string HostingStartupAssembliesKey = "hostingStartupAssemblies"; public static readonly string HostingStartupExcludeAssembliesKey = "hostingStartupExcludeAssemblies"; public static readonly string PreventHostingStartupKey = "preventHostingStartup";

也就是說,我們可以可以通過配置這三個名稱的配置,來完成HostingStartup相關的功能比如

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{//通過UseSetting的方式指定程序集的名稱webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib");//如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib;HostStartupLib2");//排除執行HostStartupLib2程序集執行HostingStartup邏輯webBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, "HostStartupLib2");//禁用HostingStartup功能webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true");webBuilder.UseStartup<Startup>();});

或通過環境變量的方式去操作

"environmentVariables": {"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib",//如果HostingStartup存在多個程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib;HostStartupLib2"//排除執行HostStartupLib2程序集執行HostingStartup邏輯"ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES":"HostStartupLib2",//禁用HostingStartup功能"ASPNETCORE_PREVENTHOSTINGSTARTUP":"true" }

其實這兩種配置方式是完全等價的,為什么這么說呢?首先是在Configuration中獲取配置是忽略大小寫的,其實是使用ConfigureWebHostDefaults配置WebHost相關信息的時候會添加configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_")邏輯這樣的話獲取環境變量的時候可以忽略ASPNETCORE_前綴。
那么到目前為止,還有一個疑問尚未解決,那就是為何只能通過UseSetting和環境變量的方式去配置HostingStartup相關配置,解鈴還須系鈴人,我們在上面的ExecuteHostingStartups方法中看到了這個邏輯

//這里傳遞了一個_config var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);

我們可以看到傳遞了配置Configuration的實例_config,我們到初始化_config地方有如下邏輯

var configBuilder = new ConfigurationBuilder().AddInMemoryCollection(); if (!options.SuppressEnvironmentConfiguration) {//添加環境變量configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); } //構建了_config實例 private readonly IConfiguration _config = configBuilder.Build();

也就可以解釋為何我們可以通過環境變量去配置HostingStartup,然后我們再來看UseSetting方法的邏輯

public IWebHostBuilder UseSetting(string key, string value) {_config[key] = value;return this; }

原來UseSetting也是給_config實例設置值,所以無論通過UseSetting或環境環境變量的方式去配置,本質都是在操作_config這個配置實例,到此為止所有謎團均以解開。

在SkyAPM中的使用

我們上面說了HostingStartup可以增強啟動時候的操作,可以通過對現有代碼無入侵的方式增強程序功能。而SkyAPM-dotnet也正是使用了這個功能,實現了無入侵啟動APM監控。我們來回顧一下SkyAPM-dotnet的使用方式

  • 首先是使用Nuget添加SkyAPM.Agent.AspNetCore程序集引用。

  • 其次是在launchSettings.json文件中添加ASPNETCORE_HOSTINGSTARTUPASSEMBLIES:"SkyAPM.Agent.AspNetCore"環境變量配置(等同于set ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=SkyAPM.Agent.AspNetCore或export ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=SkyAPM.Agent.AspNetCore
    的方式,本質都是在配置環境變量)

  • 最后通過SKYWALKING__SERVICENAME設置程序名稱
    這里我們通過需要配置ASPNETCORE_HOSTINGSTARTUPASSEMBLIES名稱可以看出確實是使用了HostingStartup功能,而通過HostingStartup增強的操作入口肯定就在SkyAPM.Agent.AspNetCore程序集中,我們找到SkyAPM.Agent.AspNetCore程序集的源碼[點擊查看源碼????]看到了SkyApmHostingStartup類實現如下

using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using SkyApm.Agent.AspNetCore; using SkyApm.AspNetCore.Diagnostics;[assembly: HostingStartup(typeof(SkyApmHostingStartup))]namespace SkyApm.Agent.AspNetCore {internal class SkyApmHostingStartup : IHostingStartup{public void Configure(IWebHostBuilder builder){builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));}} }

通過這個我們可以看出確實如此,當然也是等同于我們通過UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SkyApm.Agent.AspNetCore")去配置,我們甚至可使用如下的方式去使用SkyAPM-dotnet

public void ConfigureServices(IServiceCollection services) {services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()) }

這些寫法其實是完全等價的,但是通過環境變量的方式配置HostingStartup啟動程序集的方式無疑是最優雅的。所以我們在日常的學習開發中,最好還是通過這種方式去操作。

改造Zipkin使用

我們在之前的文章ASP.NET Core整合Zipkin鏈路跟蹤中曾演示過基于診斷日志DiagnosticSource改進Zipkin的集成方式,通過本篇文章講述的HostingStartup我們可以進步一改進Zipkin的集成方式,可以讓它使用起來和SkyAPM-dotnet類似的方式,我們基于之前的示例中的ZipkinExtensions程序集中添加一個ZipkinHostingStartup類,用于承載集成Zipkin的操作,代碼如下

using System; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection;namespace ZipkinExtensions {public class ZipkinHostingStartup: IHostingStartup{public void Configure(IWebHostBuilder builder){builder.ConfigureServices(services=> {services.AddZipkin();services.AddSingleton<ITraceDiagnosticListener, HttpDiagnosticListener>();});builder.Configure(app=> {IHostApplicationLifetime lifetime = app.ApplicationServices.GetService<IHostApplicationLifetime>();ILoggerFactory loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();IConfiguration configuration = app.ApplicationServices.GetService<IConfiguration>();string serivceName = configuration.GetValue<string>("ServiceName");string zipKinUrl = configuration.GetValue<string>("ASPNETCORE_ZIPKINADDRESS");app.UseZipkin(lifetime, loggerFactory, serivceName, zipKinUrl);});}} }

然后在每個項目的launchSettings.json文件中添加如下所示的配置即可,這樣的話就可以做到對現有業務代碼無任何入侵。

"environmentVariables": {"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "ZipkinExtensions","ASPNETCORE_ZIPKINADDRESS": "http://localhost:9411/"}

總結

????本文介紹了HostingStartup的基本概念,基礎使用以及對其源碼的分析和在SkyAPM-dotnet中的應用,最后我們改造了Zipkin的集成方式。HostingStartup在一些集成APM或者鏈路跟蹤的類似場景還是非常實用的,或者如果我們有集成一些基礎組件或者三方的組件,但是我們的代碼中并不需要直接的使用這些組件中的類或者直接的代碼關系,均可以使用HostingStartup的方式去集成,為我們實現對現有代碼提供無入侵增強提供了強大的支持。關于HostingStartup我也是在看源碼中無意發現的,后來發現微軟ASP.NET Core官方文檔Use hosting startup assemblies in ASP.NET Core一文中有講解,然后聯想到自己使用過的SkyAPM-dotnet正是使用了HostingStartup+診斷日志DiagnosticSource的方式實現了對代碼無入侵的方式進行監控和鏈路跟蹤。于是決定深入研究一下,可謂收獲滿滿,便寫下這篇文章希望更多的人能夠了解使用這個功能。

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

總結

以上是生活随笔為你收集整理的ASP.NET Core使用HostingStartup增强启动操作的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 中文字幕第12页 | 中文字幕在线网址 | 一个人看的www日本高清视频 | 一区二区精品 | 用力挺进新婚白嫩少妇 | 黄色岛国片 | av日韩在线免费观看 | 国产精品久久久久久久久久小说 | 欧美视频一二三区 | 这里只有精品66 | 亚洲大乳 | 99精品欧美一区二区蜜桃免费 | 亚洲女同二女同志 | 综合伊人av | 一边吃奶一边摸做爽视频 | 一级黄在线观看 | 男女啪啪十八 | 日本人体视频 | 亚洲乱码国产乱码精品精的特点 | 国产精品无码一区二区三区在线看 | 色婷婷久久久亚洲一区二区三区 | 国产精品精品软件视频 | 成年网站| 美丽的姑娘观看在线播放 | 99re6在线精品视频免费播放 | 亚洲精品乱码久久久久久国产主播 | 在线看片一区二区 | 在线香蕉 | 911毛片 | 国产理论片 | 伊人网狼人 | 午夜福利视频一区二区 | 久久国产色 | caobi视频| 国产精品久久久久久久免费看 | 小嫩女直喷白浆 | 67194少妇在线观看 | 久久无码人妻精品一区二区三区 | 最近日韩中文字幕中文 | 日韩在线观看 | 欧美激情影音先锋 | 日韩久久精品一区二区 | 蜜臀国产AV天堂久久无码蜜臀 | 电影91久久久 | wwwwxxxx欧美| 亚洲精品一二区 | 亚洲xxxx视频| 人妖粗暴刺激videos呻吟 | 91老师片黄在线观看 | 理论片午午伦夜理片影院99 | 中文字幕不卡在线播放 | 久久久久久久久久久久久女国产乱 | 自拍视频第一页 | 中文字幕高清在线播放 | 日韩偷拍一区 | 亚洲三级色 | 3d动漫精品啪啪一区二区三区免费 | 麻豆传媒一区二区三区 | 亚洲精品乱码久久久久久蜜桃欧美 | 午夜神马福利 | 一级在线免费观看 | 亚洲涩色 | 欧美操操 | 亚洲一级网站 | 91香蕉视频污污 | 亚洲中文字幕一区二区在线观看 | 免费的av网站 | 干欧美| 探花av在线 | 日韩欧美专区 | 久久视频免费观看 | 久久久精品视频在线观看 | 国产午夜精品在线观看 | 成人区人妻精品一区二区网站 | 巨大乳の揉んで乳榨り奶水 | 国产a网| 国产高清成人久久 | 欧洲亚洲另类 | 99视频久久 | 精品久久久久久久久久久久久久久久久久 | 日韩第一色 | 国产蜜臀av一区二区 | 国产高潮呻吟久久 | 啪啪免费小视频 | 久久99精品久久久久子伦 | 久久国产人妻一区二区免色戒电影 | 国产在线一区二区 | 亚洲一级色 | 亚洲第一天堂影院 | 天堂av手机版 | 成年黄色网 | 白洁av | 日本一二三区视频在线 | 国产高清视频网站 | 五月天激情四射 | 国产有码在线 | 一级欧美一级日韩片 | 热热热热色 | 国产精品成人久久 |