CefSharp.ChromiumWebBrowser瀏覽器的一些功能使用
1.配置支持AnyCpu編譯模式
2.使用Http代理服務
3.Cookie隔離,每個IWebBrowser實例的數據不共享
4.使用IResponseFilter獲取響應數據
1.配置支持AnyCpu編譯模式
CefSharp從51版本以后開始支持AnyCpu編譯模式,首先需要在當前項目的csproj文件的PropertyGroup節點下第一行增加一個配置項
<CefSharpAnyCpuSupport>true</CefSharpAnyCpuSupport>
然后在程序的啟動入口配置動態加載目標平台x86/x64的程序集:
[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); AppDomain.CurrentDomain.AssemblyResolve += Resolver; Application.Run(new Form1()); } private static Assembly Resolver(object sender, ResolveEventArgs args) { if (args.Name.StartsWith("CefSharp")) { string assemblyName = args.Name.Split(new[] { ',' }, 2)[0] + ".dll"; string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, Environment.Is64BitProcess ? "x64" : "x86", assemblyName); return File.Exists(archSpecificPath) ? Assembly.LoadFile(archSpecificPath) : null; } return null; }
這種方法是根據運行的目標平台動態去加載對應的程序集,如果我們能明確運行平台則可以不用加上面的代碼邏輯,在當前項目App.config文件的根節點下加入以下配置即可:
<runtime>
<assemblyBinding xmlns=
"urn:schemas-microsoft-com:asm.v1"
>
<probing privatePath=
"x64"
/><!--如果是32位系統,則換成x86-->
</assemblyBinding>
</runtime>
|
2.使用Http代理服務
網上一些文章介紹的通過添加命令行參數CefCommandLineArgs的方式,我試了一下不管用,通過 CefSharpSettings.Proxy = new ProxyOptions("ipadress", "prot", "username", "password"); 這句是可以配置成功的,但這個是全局配置,不能滿足獨立我要控制每個browser實例各自使用自己的代理服務器。通過在初始化ChromiumWebBrowser的地方加入以下代碼可實現動態設置代理。
var browser = new ChromiumWebBrowser("url", context);
browser.RequestHandler = new DefaultRequestHandler(); Cef.UIThreadTaskFactory.StartNew(delegate { var rc = browser.GetBrowser().GetHost().RequestContext; rc.GetAllPreferences(true); var dict = new Dictionary<string, object>(); dict.Add("mode", "fixed_servers"); dict.Add("server", "ipaddress:prot"); //此處替換成實際 ip地址:端口 string error; bool success = rc.SetPreference("proxy", dict, out error); if (!success) { Console.WriteLine("something happen with the prerence set up" + error); } });
如果代理服務有用戶名密碼的話,則需要在DefaultRequestHandler類里重寫GetAuthCredentials方法,如下:
protected override bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) { if (isProxy) { callback.Continue("username", "passwrod"); return true; } return false; }
這樣就可以實現每個ChromiumWebBrowser運行實例獨立連接自己的代理服務器了。
3.Cookie隔離,每個IWebBrowser實例的數據不共享
要保證多個browser實例之間cookie不共享,就不要在全局設置CefSettings中設置CachePath值,應該在實例的RequestContextSettings中設置,可以設置成每個browser擁有獨立的緩存目錄。在RequestContext內添加的cookie只有當前browser才能訪問,從而實現cookie隔離。
var setting = new RequestContextSettings() { CachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CefSharp\\Cache_" + name), PersistSessionCookies = true, PersistUserPreferences = true }; var context = new RequestContext(setting); var cookieManager = context.GetCookieManager(null); //這樣設置的cookie不是全局的,只有當前browser才能訪問 cookieManager.SetCookie("domain", new Cookie { Name = "cookiename", Value = "cookievalue", Path = "/" }); var browser = new ChromiumWebBrowser("url", context);
4.使用IResponseFilter獲取響應數據
要獲取ChromiumWebBrowser中每個請求的響應body內容並不是那么方便,拿到Frame的html內容倒是很簡單,使用IFrame的GetSourceAsync()方法就行,但有時候我們需要單個請求的響應結果,這就需要自定義實現IResponseFilter接口來實現響應數據的攔截。
//DefaultResourceHandler的構造可以放在IRequestHandler的實現類的GetResourceRequestHandler方法內
class DefaultResourceHandler : ResourceRequestHandler { protected override IResponseFilter GetResourceResponseFilter(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response) { if (response.MimeType.Equals("application/json", StringComparison.OrdinalIgnoreCase)) { return JsonResponseFilter.CreateFilter(request.Identifier.ToString()); } return null; } protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) { var filter = JsonResponseFilter.GetFileter(request.Identifier.ToString()) as JsonResponseFilter; if (filter != null) { var encode = !string.IsNullOrEmpty(response.Charset) ? Encoding.GetEncoding(response.Charset) : Encoding.UTF8; using (var read = new StreamReader(filter.GetStream(), encode)) { var text = read.ReadToEnd(); Debug.WriteLine(text); } } } } public class JsonResponseFilter : IResponseFilter { private MemoryStream Stream; public JsonResponseFilter() { Stream = new MemoryStream(); } public FilterStatus Filter(System.IO.Stream dataIn, out long dataInRead, System.IO.Stream dataOut, out long dataOutWritten) { try { if (dataIn == null || dataIn.Length == 0) { dataInRead = 0; dataOutWritten = 0; return FilterStatus.Done; } dataInRead = dataIn.Length; dataOutWritten = Math.Min(dataInRead, dataOut.Length); dataIn.CopyTo(dataOut); dataIn.Seek(0, SeekOrigin.Begin); byte[] bs = new byte[dataIn.Length]; dataIn.Read(bs, 0, bs.Length); Stream.Write(bs, 0, bs.Length); dataInRead = dataIn.Length; dataOutWritten = dataIn.Length; return FilterStatus.NeedMoreData; } catch (Exception ex) { dataInRead = dataIn.Length; dataOutWritten = dataIn.Length; return FilterStatus.Done; } } public bool InitFilter() { return true; } public Stream GetStream() { Stream.Seek(0, SeekOrigin.Begin); return Stream; } public void Dispose() { } private static Dictionary<string, IResponseFilter> _dictionary = new Dictionary<string, IResponseFilter>(); public static IResponseFilter CreateFilter(string id) { var filter = new JsonResponseFilter(); _dictionary[id] = filter; return filter; } public static IResponseFilter GetFileter(string id) { if (_dictionary.ContainsKey(id)) { var filter = _dictionary[id]; _dictionary.Remove(id); return filter; } return null; } }
為了截獲響應數據繞了這么一大圈有點費勁,不過人家這種設計也是為了方便外部擴展,可以針對不同響應類型的response來實現IResponseFilter過濾器。