ASP.NET Core 在 IIS 下的兩種部署模式

KestrelServer最大的優勢體現在它的跨平臺的能力,如果ASP.NET CORE應用只需要部署在Windows環境下,IIS也是不錯的選擇。ASP.NET CORE應用針對IIS具有兩種部署模式,它們都依賴於一個IIS針對ASP.NET CORE Core的擴展模塊。本文提供的示例演示已經同步到《ASP.NET Core 6框架揭祕-實例演示版》)

一、ASP.NET CORE Core Module
二、 In-Process部署模式
三、Out-of-Process部署模式
四、<aspnetcore>配置

一、ASP.NET CORE Core Module

IIS其實也是按照管道的方式來處理請求的,但是IIS管道和ASP.NET CORE中間件管道有本質的不同。對於部署在IIS中的Web應用來說,從最初接收到請求到最終將響應發出去,這段處理流程被細分爲一系列固定的步驟,每個都具有一個或者兩個(前置+後置)對應的事件或者回調。我們可以利用自定義的Module註冊相應的事件或回調在適當的時機接管請求,並按照自己希望的方式對它進行處理。

IIS提供了一系列原生(Native)的Module,我們也可以使用任意.NET語言編寫託管的Module,整合IIS和ASP.NET CORE 的這個ASP.NET CORE Core Module就是一個原生的Module。它利用註冊的事件將請求從IIS管道中攔截下來,並轉發給ASP.NET CORE管道進行處理。相應的安裝包可以從https://dotnet.microsoft.com/permalink/dotnetcore-current-windows-runtime-bundle-installer下載。

二、 In-Process部署模式

ASP.NET CORE在IIS下有In-Process和Out-of-Process兩種部署模式。In-Process模式下的ASP.NET CORE應用運行在IIS的工作進程w3wp.exe中(如果採用IIS Express,工作進程爲iisexpress.exe)。如圖18-7所示,ASP.NET CORE應用在這種模式下使用的服務器類型是IISHttpServer,上述的ASP.NET CORE Core Module會將原始的請求轉發給這個服務器,並將後者生成響應轉交給IIS服務器進行回覆。

ASP.NET Core Module in the in-process hosting scenario
圖1 In-Process部署模式

In-Process是默認採用的部署模式,所以我們不需要爲此做任何設置,接下來我們就來演示一下具體的部署方式。我們在IIS的默認站點(Defaut Web Site)創建一個名爲WebApp的應用,並將映射的物理路徑設置爲“C:\App”。然後我們創建一個空的ASP.NET CORE程序,並編寫了如下這個將當前進程名稱作爲響應內容的演示程序。

using System.Diagnostics;
var app = WebApplication.Create(args);
app.Run(context => context.Response.WriteAsync(Process.GetCurrentProcess().ProcessName));
app.Run();

然後我們在Visual Studio的解決方案視圖右鍵選擇該項目,在彈出的菜單中選擇“發佈(Publish)”選項,創建一個指向“C:\App”的Publish Profile,然後執行這個Profile完成發佈工作。應用發佈也可以執行命令行“dotnet public”來完成。應用部署好之後,我們利用瀏覽器採用地址“http://localhost/webapp”訪問部署好的應用,從圖2所示的輸出結果可以看出ASP.NET CORE應用實際上就運行在IIS的工作進程中。

clip_image004

圖2 In-Process模式下的進程名稱

如果我查看此時的部署目錄(“C:\App”),會發現生成的程序集和配置文件。應用既然部署在IIS中,那麼具體的配置自然定義在web.config中,如下所示的就是這個文件的內容。我們會發現所有的請求(path="*" verb="*")都被映射到“AspNetCoreModuleV2”這個Module上,這就是上面介紹的ASP.NET CORE Core Module。至於這個Module如果啓動ASP.NET CORE管道並與之交互,則由後面的<aspNetCore>配置節來控制,可以看到它將表示部署模式的hostingModel屬性設置爲“inprocess”。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\App.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: 243DF55D-2E11-481F-AA7A-141C2A75792D-->

In-Process模式會註冊如下這個IISHttpServer,對應的配置選項定義在IISServerOptions中。如果具有同步讀寫請求和響應主體內容的需要,我們需要將AllowSynchronousIO屬性(默認爲False)設置爲True。如果將AutomaticAuthentication屬性返回True(默認值),認證用戶將自動賦值給HttpContext上下文的User屬性。我們可以利用MaxRequestBodyBufferSize(默認爲1,048,576)和MaxRequestBodySize屬性(默認爲30,000,000)設置接收請求主體的緩衝區的容量,和最大請求主體的字節數。

internal class IISHttpServer : IServer, IDisposable
{
    public IFeatureCollection Features { get; }
    public IISHttpServer(IISNativeApplication nativeApplication, IHostApplicationLifetime applicationLifetime,IAuthenticationSchemeProvider authentication, IOptions<IISServerOptions> options, ILogger<IISHttpServer> logger);
    public unsafe Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken);
    public Task StopAsync(CancellationToken cancellationToken);
}
public class IISServerOptions
{
    public bool 	AllowSynchronousIO { get; set; }
    public bool 	AutomaticAuthentication { get; set; }
    public string? 	AuthenticationDisplayName { get; set; }
    public int 	        MaxRequestBodyBufferSize { get; set; }
    public long? 	MaxRequestBodySize { get; set; }
}

針對IISHttpServer的註冊實現在IWebHostBuilder接口如下這個UseIIS擴展方法中。由於這個方法並沒有提供一個Action<IISServerOptions>委託參數對IISServerOptions配置選項進行設置,所以我們不得不採用原始的對它進行設置。由於IHostBuider接口ConfigureWebHostDefaults擴展方法內部會調用這個方法, 我們並不需要爲此做額外的工作。

public static class WebHostBuilderIISExtensions
{
    public static IWebHostBuilder UseIIS(this IWebHostBuilder hostBuilder);
}

三、Out-of-Process部署模式

ASP.NET CORE應用在IIS中還可以採用Out-of -Process模式進行部署。如圖3所示,在這種部署下,採用KestrelServer的ASP.NET CORE應用運行在獨立的dotnet.exe進程中。當IIS接受到針對目標應用的請求時,如果目標應用所在的進程並未啓動,ASP.NET CORE Core Module還負責執行dotnet命令激活此進程,相當於充當了WAS(Windows Activation Service)的作用。

ASP.NET Core Module in the out-of-process hosting scenario
圖3 Out-of-Process部署模式

在激活ASP.NET CORE承載進程之前,ASP.NET CORE Core Module會選擇一個可用的端口號,該端口號和當前應用的路徑(該路徑將作用ASP.NET CORE應用的PathBase)被寫入環境變量,對應的環境變量名稱分別爲“ASPNETCORE_PORT”和“ASPNETCORE_APPL_PATH”。以Out-of-Process模式部署的ASP.NET CORE應用只會接收IIS轉發給它的請求,爲了能夠過濾其它來源的請求,ASP.NET CORE Core Module會生成一個Token並寫入環境變量“ASPNETCORE_TOKEN”。後續轉發的請求會利用一個報頭“MS-ASPNETCORE-TOKEN”傳遞此Token,ASP.NET CORE應用會校驗是否與之前生成的Token匹配。

ASP.NET CORE Core Module還會利用環境變量傳遞其他一些設置,認證方案會寫入環境變量“ASPNETCORE_IIS_HTTPAUTH”,另一個“ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED”環境變量用來設置針對Web Socket的支持狀態。由於這些環境變量名稱的前綴都是“ASPNETCORE_”,所以它們會作爲默認配置源。KestrelServer最終會綁定到基於該端口的本地終結點(“localhost”)進行監聽。由於監聽地址是由ASP.NET CORE Core Module控制的,所以它只需要將請求往該地址進行轉發,最終將接收到響應交給IIS返回即可。由於這裏涉及本地迴環網絡(Loopback)的訪問,其性能自然不如In-Process部署模式。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2"resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\App.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="outofprocess" />
    </system.webServer>
  </location>
</configuration>

我們在上面演示了In-Process的部署方式,現在我們直接修改配置文件web.config,按照上面的方式將<aspNetCore>配置節的hostingModel屬性設置爲“outofprocess”,部署的應用就自動切換到Out-of-Process。此時再次以相同的方式訪問部署的應用,我們會發現瀏覽器上顯示的進程名稱變成了“dotnet”。

clip_image008

圖4 Out-of-Process模式下的進程名稱

部署模式可以直接定義在項目文件中,如果按照如下的方式將AspNetCoreHostingModel屬性設置爲“OutOfProcess”,那麼發佈後生成的web.config中針對部署模式的設置將隨之改變。該屬性默認值爲“InProcess”,我們也可以顯式進行設置。

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
        <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
   </PropertyGroup>
</Project>

爲了進一步驗證上述的這一系列環境變量是否存在,如下所示的演示程序會將以“ASPNETCORE_”爲前綴的環境變量作爲響應內容輸出來。除此之外,作爲響應輸出的還有進程名稱、請求的PathBase和“MS-ASPNETCORE-TOKEN”報頭。

using System.Diagnostics;
using System.Text;

var app = WebApplication.Create(args);
app.Run(HandleAsync);
app.Run();

Task HandleAsync(HttpContext httpContext)
{
    var request = httpContext.Request;
    var configuration = httpContext.RequestServices.GetRequiredService<IConfiguration>();
    var builder = new StringBuilder();
    builder.AppendLine($"Process: {Process.GetCurrentProcess().ProcessName}");
    builder.AppendLine($"MS-ASPNETCORE-TOKEN: {request.Headers["MS-ASPNETCORE-TOKEN"]}");
    builder.AppendLine($"PathBase: {request.PathBase}");
    builder.AppendLine("Environment Variables");
    foreach (string key in Environment.GetEnvironmentVariables().Keys)
    {
        if (key.StartsWith("ASPNETCORE_"))
        {
            builder.AppendLine($"\t{key}={Environment.GetEnvironmentVariable(key)}");
        }
    }
    return httpContext.Response.WriteAsync(builder.ToString());
}

應用重新發布之後,再次利用瀏覽器訪問後回得到如圖5所示的結果。我們可以從這裏找到上述的環境變量,請求攜帶的“MS-ASPNETCORE-TOKEN”報頭正好與對應環境變量的值一致,應用在IIS中的虛擬目錄作爲了應用路徑被寫入環境變量併成爲請求的PathBase。如果站點提供了HTTPS終結點,其端口還會寫入“SPNETCORE_ANCM_HTTPS_PORT”這個環境變量,這是爲了實現針對HTTPS終結點的重定向而設計的。

image
圖5 Out-of-Process模式下環境變量

Out-of-Process部署的大部分實現都是由如下這個IISMiddleware中間件來完成的,IISOptions爲對應的配置選項。IISMiddleware中間件完成了針對“配對Token”的驗證過濾非IIS轉發的請求。如果IISOptions配置選項的ForwardClientCertificate屬性返回True(默認值),此中間件會從請求報頭“MS-ASPNETCORE-CLIENTCERT”中提取客戶端證書,並將它保存到ITlsConnectionFeature特性中。該中間件還會將當前Windows賬號對應的WindowsPrincipal對象附加到HttpContext上下文的特性集合中,如果IISOptions配置選項的AutomaticAuthentication屬性返回True(默認值),該對象會直接賦值給HttpContext上下文的User屬性。

public class IISMiddleware
{
    public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<IISOptions> options, string pairingToken, IAuthenticationSchemeProvider authentication, IHostApplicationLifetime applicationLifetime);
    public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<IISOptions> options, string pairingToken, bool isWebsocketsSupported, IAuthenticationSchemeProvider authentication, IHostApplicationLifetime applicationLifetime);
    public Task Invoke(HttpContext httpContext);
    public Task Invoke(HttpContext httpContext)
}
public class IISOptions
{
    public bool AutomaticAuthentication { get; set; }
    public string? AuthenticationDisplayName { get; set; }
    public bool ForwardClientCertificate { get; set; }
}

IIS利用WAS根據請求激活工作進程w3wp.exe。如果站點長時間未曾訪問,它還會自動關閉工作進程。如果工作進程都關閉了,承載ASP.NET CORE應用的dotnet.exe進程自然也應該關閉。爲了關閉應用承載進程,ASP.NET CORE Core Module會發送一個特殊的請求,該請求攜帶一個值爲“shutdown”的“MS-ASPNETCORE-EVENT”報頭,IISMiddleware中間件在接收到該請求時會利用注入的IHostApplicationLifetime對象關閉當前應用。如果不支持WebSocket,該中間件還會將代表“可升級到雙向通信”的IHttpUpgradeFeature特性刪除。將應用路徑設置爲請求的PathBase也是由這個中間件完成的。由於IISMiddleware中間件所作的實際上是對HttpContext上下文進行初始化的工作,所以它必須優先執行纔有意義,爲了將此中間件置於管道的前端,如下這個IISSetupFilter被定義出來完成對該中間件的註冊。

internal class IISSetupFilter : IStartupFilter
{
    internal IISSetupFilter(string pairingToken, PathString pathBase, bool isWebsocketsSupported);
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
}

IISSetupFilter最終是通過IWebHostBuilder接口如下這個UseIISIntegration擴展方法進行註冊的。這個方法還負責從當前配置和環境變量提取端口號,並完成監聽地址的註冊。由於KestrelServer默認會選擇註冊到服務器上的終結點,所以該方法會利用配置將IServerAddressesFeature特性的PreferHostingUrls屬性設置爲True,這裏設置的監聽地址纔會生效。這個方法還會根據當前IIS站點的設置對IISOptions作相應設置。由於IHostBuider接口ConfigureWebHostDefaults擴展方法內部也會調用這個方法,我們並不需要爲此做額外的工作。

public static class WebHostBuilderIISExtensions
{
    public static IWebHostBuilder UseIISIntegration(this IWebHostBuilder hostBuilder);
}

四、<aspnetcore>配置

不論是採用何種部署模式,相關的配置都定義在部署目錄下的web.config配置文件,它提供的針對ASP.NET CORE Core Module的映射使我們能夠將ASP.NET CORE應用部署在IIS中。在web.config中,與ASP.NET CORE應用部署相關的配置定義在<aspNetCore>配置節中。

<aspNetCore
    processPath 		= "dotnet"
    arguments 			= ".\App.dll"
    stdoutLogEnabled 		= "false"
    stdoutLogFile 		= ".\logs\stdout"
    hostingModel		= "outofprocess"
    forwardWindowsAuthToken	= "true"
    processesPerApplication	= "10"
    rapidFailsPerMinute	= "5"
    requestTimeout		= "00:02:00"
    shutdownTimeLimit		= "60"
    startupRetryCount		= "3"
    startupTimeLimit		= "60">
    <environmentVariables>
        <environmentVariable name = "ASPNETCORE_ENVIRONMENT" value = "Development"/>
    </environmentVariables>
    <handlerSettings>
        <handlerSetting name = "stackSize" value = "2097152" />
        <handlerSetting name = "debugFile" value = ".\logs\aspnetcore-debug.log" />
        <handlerSetting name = "debugLevel" value = "FILE,TRACE" />
    </handlerSettings>
</aspNetCore>

上面這段XML片段包含了完整的<aspNetCore>配置屬性,下表對這些配置進行了簡單的說明。設置的文件可以採用絕對路徑和相對於部署目錄(通過 “.”表示)的相對路徑。

屬性

含  義

processPath

ASP.NET CORE應用啓動命令所在路徑,必需。

arguments

ASP.NET CORE應用啓動傳入的參數,可選。

stdoutLogEnabled

是否將stdout 和stderr輸出到 stdoutLogFile屬性指定的文件,默認爲False。

stdoutLogFile

作爲stdout 和stderr輸出的日誌文件,默認爲“ aspnetcore-stdout”。

hostingModel

部署模式,“inprocess/InProcess”或者“outofprocess/OutOfProcess”(默認值)。

forwardWindowsAuthToken

是否轉發Windows認證令牌,默認爲True。

processesPerApplication

承載ASP.NET CORE應用的進程( processPath)數,默認爲1。該配置對In-Process模式無效。

rapidFailsPerMinute

ASP.NET CORE應用承載進程( processPath)每分鐘允許崩潰的次數,默認爲10,超過此數量將不再試圖重新啓動它。

requestTimeout

請求處理超時時間,默認爲2分鐘。

startupRetryCount

ASP.NET CORE應用承載進程啓動重試次數,默認爲2次。

startupTimeLimit

ASP.NET CORE應用承載進程啓動超時時間(單位爲秒),默認爲120秒。

environmentVariables

設置環境變量。

handlerSettings

爲ASP.NET CORE Core Module提供額外的配置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章