1. 程式人生 > >ASP.NET Core靜態檔案中介軟體[1]: 搭建檔案伺服器

ASP.NET Core靜態檔案中介軟體[1]: 搭建檔案伺服器

雖然ASP.NET Core是一款“動態”的Web服務端框架,但是由它接收並處理的大部分是針對靜態檔案的請求,最常見的是開發Web站點使用的3種靜態檔案(JavaScript指令碼、CSS樣式和圖片)。ASP.NET Core提供了3箇中間件來處理針對靜態檔案的請求,利用它們不僅可以將物理檔案釋出為可以通過HTTP請求獲取的Web資源,還可以將所在的物理目錄的結構呈現出來。通過HTTP請求獲取的Web資源大部分來源於儲存在伺服器磁碟上的靜態檔案。對於ASP.NET Core應用來說,如果將靜態檔案儲存到約定的目錄下,絕大部分檔案型別都是可以通過Web的形式對外發布的。基於靜態檔案的請求由3箇中間件負責處理,它們均定義在NuGet包“Microsoft.AspNetCore.StaticFiles”中,利用這3箇中間件完全可以搭建一個基於Web的檔案伺服器,下面做相關的例項演示。[更多關於ASP.NET Core的文章請點這裡]

目錄
一、釋出物理檔案
二、呈現目錄結構
三、顯示預設頁面
四、對映媒體型別

一、釋出物理檔案

我們建立的演示例項是一個簡單的ASP.NET Core應用,它的專案結構如下圖所示。在預設作為WebRoot的“wwwroot”目錄下,可以將JavaScript指令碼檔案、CSS樣式檔案和圖片檔案存放到對應的子目錄(js、css和img)下。WebRoot目錄下的所有檔案將自動釋出為Web資源,客戶端可以訪問相應的URL來讀取對應檔案的內容。

針對具體某個靜態檔案的請求是通過一個名為StaticFileMiddleware的中介軟體來處理的。如下面的程式碼片段所示,承載ASP.NET Core應用的程式中呼叫IApplicationBuilder介面的UseStaticFiles擴充套件方法註冊的就是這樣一箇中間件。

public class Program
{
    public static void Main()
    {
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder=>builder.Configure(app => app.UseStaticFiles()))
            .Build()
            .Run();
    }
}

上述程式執行之後,就可以通過GET請求的方式來讀取對應檔案的內容。請求採用的URL由目標檔案的路徑決定。具體來說,目標檔案相對於WebRoot目錄的路徑就是對應URL的路徑,如JPG圖片檔案“~/wwwroot/img/dolphin1.jpg”對應的URL路徑為“/img/dolphin1.jpg”。如果直接利用瀏覽器訪問這個URL,目標圖片就會直接以下圖所示的形式顯示出來。

上面通過一個簡單的例項將WebRoot所在目錄下的所有靜態檔案釋出為Web資源,如果需要釋出的靜態檔案儲存在其他目錄下呢?下面將上面演示的應用程式的一些文件儲存在下圖所示的“~/doc/”目錄下,那麼對應的程式又該如何編寫?

ASP.NET Core應用在大部分情況下都是利用一個IFileProvider物件來讀取檔案的針對靜態檔案的讀取請求也不例外。對於IApplicationBuilder介面的UseStaticFiles擴充套件方法註冊的StaticFileMiddleware中介軟體來說,它的內部維護著一個IFileProvider物件和請求路徑的對映關係。如果呼叫UseStaticFiles方法沒有指定任何引數,那麼這個對映關係的請求路徑就是應用的基地址(PathBase),對應的IFileProvider物件自然就是指向WebRoot目錄的PhysicalFileProvider物件。

上述需求可以通過顯式定製這個對映關係的方式來實現。如下面的程式碼片段所示,我們在現有程式的基礎上額外添加了一次針對UseStaticFiles擴充套件方法的呼叫,在本次呼叫中指定一個對應的Options物件(一個型別為StaticFileOptions的物件)作為引數來定製請求路徑(“/documents”)與對應IFileProvider物件(針對路徑“~/doc/”的PhysicalFileProvider物件)之間的對映關係。

public class Program
{
    public static void Main()
    {
        var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
        var options = new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(path),
            RequestPath = "/documents"
        };
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder.Configure(app => app
                .UseStaticFiles()
                .UseStaticFiles(options)))
            .Build()
            .Run();
    }
}

按照上面這段程式指定的對映關係,對於儲存在“~/doc/”目錄下的這個PDF檔案(checklist.pdf),對應URL的路徑就應該是“/documents/checklist.pdf”。如果利用瀏覽器請求這個地址時,PDF檔案的內容就會按照下圖所示的形式顯示在瀏覽器上。

二、呈現目錄結構

上面的演示例項註冊的StaticFileMiddleware中介軟體只會處理針對具體的某個靜態檔案的請求,如果利用瀏覽器傳送一個針對目錄的請求(如“http://localhost:5000/img/”),得到的將是一個狀態為“404 Not Found”的響應。如果希望瀏覽器呈現出目標目錄的結構,就可以註冊另一個名為DirectoryBrowserMiddleware的中介軟體。這個中介軟體會返回一個HTML頁面,請求目錄下的結構會以表格的形式顯示在這個頁面中。我們演示的程式可以按照如下方式呼叫IApplicationBuilder介面的UseDirectoryBrowser擴充套件方法來註冊DirectoryBrowserMiddleware中介軟體。

public class Program
{
    public static void Main()
    {
        var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
        var fileProvider = new PhysicalFileProvider(path);

        var fileOptions = new StaticFileOptions
        {
            FileProvider = fileProvider,
            RequestPath = "/documents"
        };

        var diretoryOptions = new DirectoryBrowserOptions
        {
            FileProvider = fileProvider,
            RequestPath = "/documents"
        };

        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder.Configure(app => app
                .UseStaticFiles()
                .UseStaticFiles(fileOptions)
                .UseDirectoryBrowser()
                .UseDirectoryBrowser(diretoryOptions)))
            .Build()
            .Run();
    }
}

當上面的應用啟動之後,如果利用瀏覽器向針對某個目錄的URL(如“http://localhost:5000/”或者“http://localhost:5000/img/”)發起請求,目標目錄的內容(包括子目錄和檔案)就會以圖14-5所示的形式顯示在一個表格中。可以看出,在呈現的表格中,當前目錄的子目錄和檔案均會顯示為連結。

三、顯示預設頁面

從安全的角度來講,利用註冊的UseDirectoryBrowser中介軟體會將整個目標目錄的結構和所有檔案全部暴露出來,所以這個中介軟體需要根據自身的安全策略謹慎使用。對於針對目錄的請求,更加常用的處理策略就是顯示一個儲存在這個目錄下的預設頁面。預設頁面檔案一般採用如下4種命名約定:default.htm、default.html、index.htm和index.html。針對預設頁面的呈現實現在一個名為DefaultFilesMiddleware的中介軟體中,我們演示的這個應用就可以按照如下方式呼叫IApplicationBuilder介面的UseDefaultFiles擴充套件方法來註冊這個中介軟體。

public class Program
{
    public static void Main()
    {
        var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
        var fileProvider = new PhysicalFileProvider(path);

        var fileOptions = new StaticFileOptions
        {
            FileProvider = fileProvider,
            RequestPath = "/documents"
        };
        var diretoryOptions = new DirectoryBrowserOptions
        {
            FileProvider = fileProvider,
            RequestPath = "/documents"
        };
        var defaultOptions = new DefaultFilesOptions
        {
            RequestPath = "/documents",
            FileProvider = fileProvider,
        };

        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder.Configure(app => app
                .UseDefaultFiles()
                .UseDefaultFiles(defaultOptions)
                .UseStaticFiles()
                .UseStaticFiles(fileOptions)
                .UseDirectoryBrowser()
                .UseDirectoryBrowser(diretoryOptions)))
            .Build()
            .Run();
    }
}

下面在“~/wwwroot/img/”目錄和“~/doc”目錄下分別建立一個名為index.html的預設頁面,並且在該.html檔案的主體部分指定一段簡短的文字(This is an index page!)。在應用啟動之後,可以利用瀏覽器訪問這兩個目錄對應的URL(“http://localhost:5000/img/”和“http://localhost:5000/documents/”),下圖顯示的就是這個預設頁面的內容。

必須在註冊StaticFileMiddleware中介軟體和DirectoryBrowserMiddleware中介軟體之前註冊DefaultFilesMiddleware中介軟體,否則它無法發揮作用。這是因為DirectoryBrowserMiddleware中介軟體和DefaultFilesMiddleware中介軟體處理的均是針對目錄的請求,如果先註冊DirectoryBrowserMiddleware中介軟體,那麼顯示的總是目錄的結構;如果先註冊用於顯示預設頁面的DefaultFilesMiddleware中介軟體,那麼在預設頁面不存在的情況下它會將請求分發給後續中介軟體,而DirectoryBrowserMiddleware中介軟體會接收請求的處理並將當前目錄的結構呈現出來。

要先於StaticFileMiddleware中介軟體之前註冊DefaultFilesMiddleware中介軟體是因為後者是通過採用URL重寫的方式實現的,也就是說,這個中介軟體會將針對目錄的請求改寫成針對預設頁面的請求,而最終針對預設頁面的請求還需要依賴StaticFileMiddleware中介軟體來完成。DefaultFilesMiddleware中介軟體在預設情況下總是以約定的名稱(default.htm、default.html、index.htm和index.html)在當前請求的目錄下定位預設頁面。如果作為預設頁面的檔案沒有采用這樣的約定命名(如我們將預設頁面命名為readme.html),就需要按照如下方式顯式指定預設頁面的檔名。

public class Program
{
    public static void Main()
    {
        var path = Path.Combine(Directory.GetCurrentDirectory(), "doc");
        var fileProvider = new PhysicalFileProvider(path);
        var fileOptions = new StaticFileOptions
        {
            FileProvider = fileProvider,
            RequestPath = "/documents"
        };
        var diretoryOptions = new DirectoryBrowserOptions
        {
            FileProvider = fileProvider,
            RequestPath = "/documents"
        };
        var defaultOptions1 = new DefaultFilesOptions();
        var defaultOptions2 = new DefaultFilesOptions
        {
            RequestPath = "/documents",
            FileProvider = fileProvider,
        };

       defaultOptions1.DefaultFileNames.Add("readme.html");
        defaultOptions2.DefaultFileNames.Add("readme.html");

        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder.Configure(app => app
                .UseDefaultFiles(defaultOptions1)
                .UseDefaultFiles(defaultOptions2)
                .UseStaticFiles()
                .UseStaticFiles(fileOptions)
                .UseDirectoryBrowser()
                .UseDirectoryBrowser(diretoryOptions)))
            .Build()
            .Run();
    }
}

四、對映媒體型別

通過上面演示的例項可以看出,瀏覽器能夠準確地將請求的目標檔案的內容正常呈現出來。對HTTP協議具有基本瞭解的讀者應該都知道:響應檔案能夠在瀏覽器上被正常顯示的基本前提是響應報文通過Content-Type報頭攜帶的媒體型別必須與內容一致。我們的例項演示了針對兩種檔案型別的請求,一種是JPG檔案,另一種是PDF檔案,對應的媒體型別分別是image/jpg和application/pdf,那麼用來處理靜態檔案請求的StaticFileMiddleware中介軟體是如何解析出對應的媒體型別的?

StaticFileMiddleware中介軟體針對媒體型別的解析是通過一個IContentTypeProvider物件來完成的,預設採用的是該介面的實現型別FileExtensionContentTypeProvider。顧名思義,FileExtensionContentTypeProvider根據檔案的擴充套件命名來解析媒體型別。FileExtensionContentTypeProvider內部預定了數百種常用副檔名與對應媒體型別之間的對映關係,所以如果釋出的靜態檔案具有標準的副檔名,那麼StaticFileMiddleware中介軟體就能為對應的響應賦予正確的媒體型別。

如果某個檔案的副檔名沒有在預定義的對映之中,或者需要某個預定義的副檔名匹配不同的媒體型別,那麼應該如何解決?同樣是針對我們演示的這個例項,筆者將~/wwwroot/img/ dolphin1.jpg檔案的副檔名改成.img,毫無疑問,StaticFileMiddleware中介軟體將無法為針對該檔案的請求解析出正確的媒體型別。這個問題具有若干不同的解決方案,第一種方案就是按照如下方式讓StaticFileMiddleware中介軟體支援不能識別的檔案型別,併為它們設定一個預設的媒體型別。

public class Program
{
    public static void Main()
    {
        var options = new StaticFileOptions
        {
            ServeUnknownFileTypes = true,
            DefaultContentType = "image/jpg"
        };

        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder.Configure(app => app.UseStaticFiles(options)))
            .Build()
            .Run();
    }
}

上述解決方案只能設定一種預設媒體型別,如果具有多種需要對映成不同媒體型別的檔案型別,採用這種方案就達不到目的,所以最根本的解決方案還是需要將不能識別的檔案型別和對應的媒體型別進行對映。由於StaticFileMiddleware中介軟體使用的IContentTypeProvider物件是可以定製的,所以可以按照如下方式顯式地為該中介軟體指定一個FileExtensionContentTypeProvider物件,然後將缺失的對映新增到這個物件上。

public class Program
{
    public static void Main()
    {
        var contentTypeProvider = new FileExtensionContentTypeProvider();
        contentTypeProvider.Mappings.Add(".img", "image/jpg");
        var options = new StaticFileOptions
        {
            ContentTypeProvider = contentTypeProvider
        };
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder.Configure(app => app.UseStaticFiles(options)))
            .Build()
            .Run();
    }
}

靜態檔案中介軟體[1]: 搭建檔案伺服器
靜態檔案中介軟體[2]: 條件請求以提升效能
靜態檔案中介軟體[3]: 區間請求以提供部分內容
靜態檔案中介軟體[4]: StaticFileMiddleware
靜態檔案中介軟體[5]: DirectoryBrowserMiddleware & DefaultFilesMiddle