大家好,我是沙漠盡頭的狼。 上文介紹了《C#使用CefSharp內嵌網頁-並給出C#與JS的交互示例》,本文介紹CefSharp的緩存實現,先來說說添加緩存的好處: 提高頁面載入加速:CefSharp緩存可以緩存已經載入過的頁面和資源,當用戶再次訪問相同的頁面時,可以直接從緩存中載入,而不需要重新下 ...
大家好,我是沙漠盡頭的狼。
上文介紹了《C#使用CefSharp內嵌網頁-並給出C#與JS的交互示例》,本文介紹CefSharp的緩存實現,先來說說添加緩存的好處:
- 提高頁面載入加速:CefSharp緩存可以緩存已經載入過的頁面和資源,當用戶再次訪問相同的頁面時,可以直接從緩存中載入,而不需要重新下載和解析頁面和資源,從而加快頁面載入速度。
- 減少網路流量:使用緩存可以減少網路流量,因為已經下載過的資源可以直接從緩存中讀取,而不需要重新下載。
- 提高用戶體驗:由於緩存可以提高頁面載入速度,因此可以提高用戶的體驗,用戶可以更快地訪問頁面和資源,從而更加愉快地使用應用程式。
- 減少伺服器負載:使用緩存可以減少伺服器的負載,因為已經下載過的資源可以直接從緩存中讀取,而不需要重新生成和發送。
- 離線訪問:可以使應用程式支持離線訪問,因為它可以緩存已經下載過的頁面和資源,當用戶沒有網路連接時,可以直接從緩存中載入頁面和資源。
總之,使用緩存可以提高應用程式的性能和用戶體驗,減少網路流量和伺服器負載,並支持離線訪問,是一個非常有用的特性。
本文示例:Github
斷網情況下,演示載入已經緩存的百度、百度翻譯、Dotnet9首頁、Dotnet9關於4個頁面:
接下來講解緩存的實現方式。
1. 預設緩存實現
CefSharp的預設緩存實現方式是基於Chromium的緩存機制。Chromium使用了兩種類型的緩存:記憶體緩存和磁碟緩存。
1.1. 記憶體緩存
記憶體緩存是一個基於LRU(最近最少使用)演算法的緩存,它緩存了最近訪問的頁面和資源。記憶體緩存的大小是有限的,當緩存達到最大大小時,最近最少使用的頁面和資源將被刪除。
記憶體緩存無法通過CefSharp.WPF的API進行設置。具體來說,Chromium會在記憶體中維護一個LRU(Least Recently Used)緩存,用於存儲最近訪問的網頁數據。當緩存空間不足時,Chromium會根據LRU演算法自動清除最近最少使用的緩存數據,以騰出空間存儲新的數據。
在CefSharp.WPF中,我們可以通過調用Cef.GetGlobalRequestContext().ClearCacheAsync()方法來清除記憶體緩存中的數據。該方法會清除所有緩存數據,包括記憶體緩存和磁碟緩存。如果只需要清除記憶體緩存,可以調用Cef.GetGlobalRequestContext().ClearCache(CefCacheType.MemoryCache)方法。
需要註意的是,由於記憶體緩存是由Chromium自身維護的,因此我們無法直接控制其大小。如果需要控制緩存大小,可以通過設置磁碟緩存的大小來間接控制記憶體緩存的大小。
1.2. 磁碟緩存
磁碟緩存是一個基於文件系統的緩存,它緩存了已經下載的頁面和資源。磁碟緩存的大小也是有限的,當緩存達到最大大小時,最早的頁面和資源將被刪除。
CefSharp.WPF的磁碟緩存是通過設置CefSettings中的CachePath屬性來實現的。具體來說,我們可以通過以下代碼設置磁碟緩存的路徑:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// CachePath需要為絕對路徑
var settings = new CefSettings
{
CachePath = $"{AppDomain.CurrentDomain.BaseDirectory}DefaultCaches"
};
Cef.Initialize(settings);
}
}
緩存目錄結構如下:
其中,CachePath屬性指定了磁碟緩存的路徑(絕對路徑)。如果不設置該屬性,Chromium會將緩存數據存儲在預設路徑下(通常是用戶目錄下的AppData\Local\CefSharp目錄)。
需要註意的是,磁碟緩存的大小是由Chromium自身控制的,我們可以通過設置CacheController的SetCacheLimit方法來控制緩存數據存儲在磁碟上的最大空間。該方法接受一個long類型的參數,表示緩存數據的最大大小(單位為位元組)。例如,以下代碼將磁碟緩存的最大大小設置為100MB:
var cacheController = Cef.GetGlobalRequestContext().CacheController;
cacheController.SetCacheLimit(100 * 1024 * 1024); // 100MB
需要註意的是,Chromium會根據LRU演算法自動清除最近最少使用的緩存數據,以騰出空間存儲新的數據。因此,即使設置了緩存大小,也不能保證所有數據都會被緩存。如果需要清除磁碟緩存中的數據,可以調用Cef.GetGlobalRequestContext().ClearCacheAsync()方法。
預設的緩存站長研究不多,上面的代碼和描述通過ChatGPT搜索得來,我們來看自定義緩存的實現,預設緩存只是個引子。
2. 自定義緩存
這是本文介紹的重點,相對於預設緩存,自定義緩存
有以下好處:
- 更加靈活:可以根據應用程式的需求來靈活地配置緩存策略和緩存大小,從而更好地滿足應用程式的需求。
- 更好的性能:可以根據應用程式的需求和特定的場景進行配置,以獲得更好的性能。預設的緩存可能不適合某些特定的場景或者不適合您的應用程式的需求,而自定義緩存則可以根據您的需求進行調整,以獲得更好的性能。
- 更好的安全性:可以更好地保護用戶的隱私和安全,因為可以控制緩存中存儲的內容和緩存的生命周期。
- 更加可控:可以更好地控制緩存的行為,例如可以控制緩存的清除時間和清除策略,從而更好地管理緩存。
- 更好的相容性:可以更好地適應不同的瀏覽器和設備,預設的緩存可能不能提供足夠的相容性,而自定義緩存則可以根據您的需求進行調整,以提供更好的相容性。
- 更加高效:可以更好地利用系統資源,例如可以使用更快的存儲設備來存儲緩存,從而提高緩存的讀寫速度。
總結:自定義緩存可以提供更好的性能、響應性、安全性和相容性,從而提高應用程式的質量和用戶體驗,人話就是更好的操控
。
2.1. 代碼實現
註釋前面加的預設緩存代碼。
2.1.1. 註冊資源請求攔截處理程式
首先在使用ChromiumWebBrowser
控制項的後臺代碼里,註冊請求攔截處理程式,CefBrowser
是控制項名,CefRequestHandlerc
是處理程式:
public TestCefCacheView()
{
InitializeComponent();
var handler = new CefRequestHandlerc();
CefBrowser.RequestHandler = handler;
}
2.1.2. 請求攔截處理程式
CefSharp里的IRequestHandler
是一個介面,用於處理瀏覽器發出的請求。它定義了一些方法,可以在請求被髮送到伺服器之前或之後對請求進行處理。
IRequestHandler
的實現類可以用於以下幾個方面:
-
攔截請求:可以通過實現OnBeforeBrowse方法來攔截請求,從而控制瀏覽器的行為。例如,可以在請求被髮送到伺服器之前檢查請求的URL,如果不符合要求,則可以取消請求或者重定向到其他頁面。
-
修改請求:可以通過實現OnBeforeResourceLoad方法來修改請求,例如可以添加一些自定義的HTTP頭信息,或者修改請求的URL。
-
處理響應:可以通過實現OnResourceResponse方法來處理伺服器返回的響應,例如可以檢查響應的狀態碼和內容,從而決定是否繼續載入頁面。
-
緩存控制:可以通過實現OnQuotaRequest方法來控制緩存的大小和清除策略,從而優化緩存的使用。
總之,IRequestHandler
的實現類可以用於控制瀏覽器的行為,優化網路請求和緩存的使用,從而提高應用程式的性能和用戶體驗。
我們不直接實現介面IRequestHandler
,而是繼承它的一個預設實現類RequestHandler
,這可以簡化我們的開發,畢竟實現介面要列出一系列介面方法。
我們重載方法GetResourceRequestHandler
, 在這個方法里返回CefResourceRequestHandler
實例,頁面中資源請求時會調用此方法:
using CefSharp;
using CefSharp.Handler;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefRequestHandlerc : RequestHandler
{
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame,
IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
// 一個請求用一個CefResourceRequestHandler
return new CefResourceRequestHandler();
}
}
2.1.3. 資源請求攔截程式
在CefSharp中,IResourceRequestHandler
介面是用於處理資源請求的,它可以攔截瀏覽器發出的資源請求,例如圖片、CSS、JavaScript等,從而實現對資源請求的控制和優化。
具體來說,IResourceRequestHandler
介面定義了一些方法,例如OnBeforeResourceLoad
、OnResourceResponse
等方法,這些方法可以用於攔截請求、修改請求、處理響應等方面。例如:
-
OnBeforeResourceLoad:在瀏覽器請求資源之前被調用,可以用於修改請求,例如添加一些自定義的HTTP頭信息,或者修改請求的URL。
-
OnResourceResponse:在瀏覽器接收到伺服器返回的響應之後被調用,可以用於處理響應,例如檢查響應的狀態碼和內容,從而決定是否繼續載入頁面。
-
OnResourceLoadComplete:在資源載入完成後被調用,可以用於處理資源載入完成後的操作,例如保存資源到本地緩存。
通過實現IResourceRequestHandler
介面,可以對資源請求進行攔截和優化,從而提高應用程式的性能和用戶體驗。
這裡我們也不直接實現IResourceRequestHandler
介面,我們定義CefResourceRequestHandler
類,繼承該介面的預設實現類ResourceRequestHandler
。
在下麵的CefResourceRequestHandler
類中:
GetResourceHandler
方法:處理資源是否需要緩存,返回null不緩存,返回CefResourceHandler
表示需要緩存,在這個類中做跨域處理。GetResourceResponseFilter
方法:註冊資源緩存的操作類,即資源下載的實現。OnBeforeResourceLoad
方法:在這個方法里,我們可以實現給頁面傳遞header參數。
using System.Collections.Specialized;
using CefSharp;
using CefSharp.Handler;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefResourceRequestHandler : ResourceRequestHandler
{
private string _localCacheFilePath;
private bool IsLocalCacheFileExist => System.IO.File.Exists(_localCacheFilePath);
protected override IResourceHandler? GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser,
IFrame frame, IRequest request)
{
try
{
_localCacheFilePath = CacheFileHelper.CalculateResourceFileName(request.Url, request.ResourceType);
if (string.IsNullOrWhiteSpace(_localCacheFilePath))
{
return null;
}
}
catch
{
return null;
}
if (!IsLocalCacheFileExist)
{
return null;
}
return new CefResourceHandler(_localCacheFilePath);
}
protected override IResponseFilter? GetResourceResponseFilter(IWebBrowser chromiumWebBrowser, IBrowser browser,
IFrame frame,
IRequest request, IResponse response)
{
return IsLocalCacheFileExist ? null : new CefResponseFilter { LocalCacheFilePath = _localCacheFilePath };
}
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser,
IFrame frame, IRequest request,
IRequestCallback callback)
{
var headers = new NameValueCollection(request.Headers);
headers["Authorization"] = "Bearer xxxxxx.xxxxx.xxx";
request.Headers = headers;
return CefReturnValue.Continue;
}
}
2.1.4. CefResourceHandler
在CefSharp中,IResourceHandler
介面是用於處理資源的,它可以攔截瀏覽器發出的資源請求,並返回自定義的資源內容,從而實現對資源的控制和優化。
具體來說,IResourceHandler
介面定義了一些方法,例如ProcessRequest
、GetResponseHeaders
、ReadResponse
等方法,這些方法可以用於處理資源請求、獲取響應頭信息、讀取響應內容等方面。例如:
-
ProcessRequest:在瀏覽器請求資源時被調用,可以用於處理資源請求,例如從本地緩存中讀取資源內容,或者從網路中下載資源內容。
-
GetResponseHeaders:在瀏覽器請求資源時被調用,可以用於獲取響應頭信息,例如設置響應的MIME類型、緩存策略等。
-
ReadResponse:在瀏覽器請求資源時被調用,可以用於讀取響應內容,例如從本地緩存中讀取資源內容,或者從網路中下載資源內容。
通過實現IResourceHandler介面,可以對資源進行自定義處理,例如從本地緩存中讀取資源內容,從而提高應用程式的性能和用戶體驗。
這裡我們也不直接實現IResourceHandler
介面,我們定義CefResourceHandler
類,繼承該介面的預設實現類ResourceHandler
。
在CefResourceHandler
的構造函數里只處理跨域問題,其他需求可通過上面介面的方法查找資料處理即可:
using CefSharp;
using System.IO;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefResourceHandler : ResourceHandler
{
public CefResourceHandler(string filePath, string mimeType = null, bool autoDisposeStream = false,
string charset = null) : base()
{
if (string.IsNullOrWhiteSpace(mimeType))
{
var fileExtension = Path.GetExtension(filePath);
mimeType = Cef.GetMimeType(fileExtension);
mimeType = mimeType ?? DefaultMimeType;
}
var stream = File.OpenRead(filePath);
StatusCode = 200;
StatusText = "OK";
MimeType = mimeType;
Stream = stream;
AutoDisposeStream = autoDisposeStream;
Charset = charset;
Headers.Add("Access-Control-Allow-Origin", "*");
}
}
2.1.5. CefResponseFilter
在CefSharp中,IResponseFilter
介面是用於過濾響應內容的,它可以攔截瀏覽器接收到的響應內容,並對其進行修改或者過濾,從而實現對響應內容的控制和優化。
具體來說,IResponseFilter
介面定義了一些方法,例如InitFilter
、Filter
、GetSize
等方法,這些方法可以用於初始化過濾器、過濾響應內容、獲取過濾後的響應內容大小等方面。例如:
-
InitFilter:在瀏覽器接收到響應內容時被調用,可以用於初始化過濾器,例如設置過濾器的狀態、獲取響應頭信息等。
-
Filter:在瀏覽器接收到響應內容時被調用,可以用於過濾響應內容,例如修改響應內容、刪除響應內容等。
-
GetSize:在瀏覽器接收到響應內容時被調用,可以用於獲取過濾後的響應內容大小,例如用於計算響應內容的壓縮比例等。
站長使用的CefSharp.Wpf
的89.0.170.0
版本中的IResponseFilter
介面沒有GetSize
方法。在該版本中,IResponseFilter
介面只定義了兩個方法:InitFilter
和Filter
。
如果在該版本中您需要獲取過濾後的響應內容大小,可以考慮在Filter
方法中自行計算。例如,在Filter
方法中,您可以將過濾後的響應內容寫入一個緩衝區,並記錄緩衝區的大小,最後返回過濾後的響應內容和緩衝區的大小。
public class MyResponseFilter : IResponseFilter
{
private MemoryStream outputStream = new MemoryStream();
public void Dispose()
{
outputStream.Dispose();
}
public bool InitFilter()
{
return true;
}
public FilterStatus Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten)
{
dataInRead = 0;
dataOutWritten = 0;
byte[] buffer = new byte[4096];
int bytesRead = 0;
do
{
bytesRead = dataIn.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
outputStream.Write(buffer, 0, bytesRead);
}
} while (bytesRead > 0);
byte[] outputBytes = outputStream.ToArray();
dataOut.Write(outputBytes, 0, outputBytes.Length);
dataInRead = outputBytes.Length;
dataOutWritten = outputBytes.Length;
return FilterStatus.Done;
}
public int GetResponseFilterBufferSize()
{
return 0;
}
public int GetResponseFilterDelay()
{
return 0;
}
}
在上述示例代碼中,我們在Filter
方法中將過濾後的響應內容寫入了一個MemoryStream
對象中,並記錄了緩衝區的大小。最後,我們在Filter方法的返回值中返回了過濾後的響應內容和緩衝區的大小。
總結,通過實現IResponseFilter
介面,可以對響應內容進行自定義處理,例如對響應內容進行壓縮、加密等操作,從而提高應用程式的性能和安全性。
本文示例這裡定義類CefResponseFilter
直接實現介面處理文件緩存實際操作類,即資源下載實現:
using CefSharp;
using System.IO;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefResponseFilter : IResponseFilter
{
public string LocalCacheFilePath { get; set; }
private const int BUFFER_LENGTH = 1024;
private bool isFailCacheFile;
public FilterStatus Filter(Stream? dataIn, out long dataInRead, Stream? dataOut, out long dataOutWritten)
{
dataInRead = 0;
dataOutWritten = 0;
if (dataIn == null)
{
return FilterStatus.NeedMoreData;
}
var length = dataIn.Length;
var data = new byte[BUFFER_LENGTH];
var count = dataIn.Read(data, 0, BUFFER_LENGTH);
dataInRead = count;
dataOutWritten = count;
dataOut?.Write(data, 0, count);
try
{
CacheFile(data, count);
}
catch
{
// ignored
}
return length == dataIn.Position ? FilterStatus.Done : FilterStatus.NeedMoreData;
}
public bool InitFilter()
{
try
{
var dirPath = Path.GetDirectoryName(LocalCacheFilePath);
if (!string.IsNullOrWhiteSpace(dirPath) && !Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
}
catch
{
// ignored
}
return true;
}
public void Dispose()
{
}
private void CacheFile(byte[] data, int count)
{
if (isFailCacheFile)
{
return;
}
try
{
if (!File.Exists(LocalCacheFilePath))
{
using var fs = File.Create(LocalCacheFilePath);
fs.Write(data, 0, count);
}
else
{
using var fs = File.Open(LocalCacheFilePath, FileMode.Append);
fs.Write(data,0,count);
}
}
catch
{
isFailCacheFile = true;
File.Delete(LocalCacheFilePath);
}
}
}
2.1.6. CacheFileHelper
緩存文件幫助類,用於管理頁面的ajax介面緩存白名單、緩存文件路徑規範等:
using CefSharp;
using System;
using System.Collections.Generic;
using System.IO;
namespace WpfWithCefSharpCacheDemo.Caches;
internal static class CacheFileHelper
{
private const string DEV_TOOLS_SCHEME = "devtools";
private const string DEFAULT_INDEX_FILE = "index.html";
private static HashSet<string> needInterceptedAjaxInterfaces = new();
private static string CachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "caches");
public static void AddInterceptedAjaxInterfaces(string url)
{
if (needInterceptedAjaxInterfaces.Contains(url))
{
return;
}
needInterceptedAjaxInterfaces.Add(url);
}
private static bool IsNeedInterceptedAjaxInterface(string url, ResourceType resourceType)
{
var uri = new Uri(url);
if (DEV_TOOLS_SCHEME == url)
{
return false;
}
if (ResourceType.Xhr == resourceType && !needInterceptedAjaxInterfaces.Contains(url))
{
return false;
}
return true;
}
public static string? CalculateResourceFileName(string url, ResourceType resourceType)
{
if (!IsNeedInterceptedAjaxInterface(url, resourceType))
{
return default;
}
var uri = new Uri(url);
var urlPath = uri.LocalPath;
if (urlPath.StartsWith("/"))
{
urlPath = urlPath.Substring(1);
}
var subFilePath = urlPath;
if (ResourceType.MainFrame == resourceType || string.IsNullOrWhiteSpace(urlPath))
{
subFilePath = Path.Combine(urlPath, DEFAULT_INDEX_FILE);
}
var hostCachePath = Path.Combine(CachePath, uri.Host);
var fullFilePath = Path.Combine(hostCachePath, subFilePath);
return fullFilePath;
}
}
自定義緩存的子目錄以資源的功能變數名稱(Host)為目錄名稱創建:
打開緩存的dotnet9.com目錄,通過查看目錄結構和程式發佈目錄基本一致,這更適合人看了,是不?
2.2. 可能存在的問題
第一點,站長目前遇到的問題,後面4點由Token AI提供解釋。
2.2.1. 對緩存的資源URL帶QueryString的方式支持不好
建議用Route(路由的方式:https://dotnet9.com/albums/wpf)代替QueryString(查詢參數的試工:https://dotnet9.com/albums?slug=wpf)的方式,站長有空再研究下QueryString的緩存方式。
如果確實資源帶QueryString,那對於這種資源就放開緩存,直接通過網路請求吧。
2.2.2. 緩存一致性問題
如果自定義緩存不正確地處理了緩存一致性,可能會導致瀏覽器顯示過期的內容或者不一致的內容。例如,如果緩存了一個網頁,但是該網頁在伺服器上已經被更新了,如果自定義緩存沒有正確地處理緩存一致性,可能會導致瀏覽器顯示過期的網頁內容。
2.2.3. 緩存空間問題
如果自定義緩存沒有正確地管理緩存空間,可能會導致瀏覽器占用過多的記憶體或者磁碟空間。例如,如果自定義緩存緩存了大量的數據,但是沒有及時清理過期的數據或者限制緩存的大小,可能會導致瀏覽器占用過多的記憶體或者磁碟空間。
2.2.4. 緩存性能問題
如果自定義緩存沒有正確地處理緩存性能,可能會導致瀏覽器的性能下降。例如,如果自定義緩存沒有正確地處理緩存的讀取和寫入,可能會導致瀏覽器的響應速度變慢。
2.2.5. 緩存安全問題
如果自定義緩存沒有正確地處理緩存安全,可能會導致瀏覽器的安全性受到威脅。例如,如果自定義緩存緩存了敏感數據,但是沒有正確地處理緩存的加密和解密,可能會導致敏感數據泄露。
因此,在自定義緩存時,需要註意處理緩存一致性、緩存空間、緩存性能和緩存安全等問題,以確保瀏覽器的正常運行和安全性。
參考:
-
微信技術交流群:添加微信(dotnet9com)備註“入群”
-
QQ技術交流群:771992300。