1.配置支援AnyCpu編譯模式
2.使用Http代理服務
3.Cookie隔離,每個IWebBrowser執行個體的資料不共享
4.使用IResponseFilter擷取響應資料
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>
網上一些文章介紹的通過添加指令行參數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運作執行個體獨立連接配接自己的代理伺服器了。
要保證多個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);
要擷取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過濾器。