大多使用CefSharp进行开发的软件都不是单纯用作浏览器的,一般都会对请求和响应进行处理,这里介绍几种常见用法:
3.1 读取网页源代码
在页面加载完成后处理, 依赖最低环境 4.5.2
async void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) { Log.WriteLog("browser_FrameLoadEnd:" + e.Url); var result = await browser.GetSourceAsync(); }
如果想在4.0下环境操作需要使用。
var task = browser.GetSourceAsync(); task.Wait(); string content = task.Result;
3.2 获取页面中的指定文件内容(.jpg,.js等)
A.首先需要对ChromiumWebBrowser 的 IRequestHandler RequestHandler进行实现。
B.需要对 IRequestHandler 的IResponseFilter IRequestHandler.GetResourceResponseFilter 方法进行重写。
C.需要写一个类实现 IResponseFilter 接口。
D.然后就可用在IResponseFilter 的
FilterStatus Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) 方法中,读取指定内容的流了。
3.3 过滤某些页面内容,例如图片或某些文字
具体实现,参考 3.2 ,在最后的Filter方法中,对返回的dataOut不进行赋值或者,取到值,然后replace处理,返回其他数据,即可。
3.4 文件进一步获取,获取完整内容
说明:由于很多文件无法获取到完整内容,再者具体文件内容在Filter里面进行了控制,而Fileter的内容依赖于IRequestHandler,所以,外部只能操作Handler得到数据。 所以需要在,Filter和Hanlder类中,使用事件来传递具体的内容。代码如下。 Filter类如下:
public class TestImageFilter : IResponseFilter { public event Action<byte[]> NotifyData; private int contentLength = 0; private List<byte> dataAll = new List<byte>(); public void SetContentLength(int contentLength) { this.contentLength = contentLength; } public FilterStatus Filter(System.IO.Stream dataIn, out long dataInRead, System.IO.Stream dataOut, out long dataOutWritten) { try { if (dataIn == null) { 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); dataAll.AddRange(bs); if (dataAll.Count == this.contentLength) { // 通过这里进行通知 NotifyData(dataAll.ToArray()); return FilterStatus.Done; } else if (dataAll.Count < this.contentLength) { dataInRead = dataIn.Length; dataOutWritten = dataIn.Length; return FilterStatus.NeedMoreData; } else { return FilterStatus.Error; } } catch (Exception ex) { dataInRead = dataIn.Length; dataOutWritten = dataIn.Length; return FilterStatus.Done; } } public bool InitFilter() { return true; } }
Filter类有了,那我们如何知道数据流的具体长度呢?这就需要在Handler的实现的其他方法里面寻找了。
bool IRequestHandler.OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) { //NOTE: You cannot modify the response, only the request // You can now access the headers //var headers = response.ResponseHeaders; try { var content_length = int.Parse(response.ResponseHeaders["Content-Length"]); if (this.filter != null) { this.filter.SetContentLength(content_length); } } catch { } return false; } private TestImageFilter filter = null; public event Action<byte[]> NotifyData; IResponseFilter IRequestHandler.GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) { var url = new Uri(request.Url); if (url.AbsoluteUri.Contains("http://test.test.com/somehead?")) { this.filter = new TestImageFilter(); filter.NotifyData += filter_NotifyData; return filter; } return null; } void filter_NotifyData(byte[] data) { if (NotifyData != null) { NotifyData(data); } }
此方法位IRequestHandler的一部分实现,通过实现函数:IRequestHandler.GetResourceResponseFilter得到资源文件的长度,然后长度传入Filter,在Filter中控制从而得到整个数据的真正长度。
3.5 文件进一步获取,获取完整内容(优化,Content-Length不一致)
由于,部分站点,返回数据是分片了的,即:不能通过,Content-Length的长度来判断,程序的流是否完成。
所以需要其他方式处理,即:单个http请求完成的时候,会调用Complete方法,所以可以在这里处理。
下面是测试代码: Filter管理类
public class FilterManager { private static Dictionary<string, IResponseFilter> dataList = new Dictionary<string, IResponseFilter>(); public static IResponseFilter CreateFilter(string guid) { lock (dataList) { var filter = new TestImageFilter(); dataList.Add(guid, filter); return filter; } } public static IResponseFilter GetFileter(string guid) { lock (dataList) { return dataList[guid]; } } }
TestFilter类,对流进行合并
public class TestImageFilter : IResponseFilter { public List<byte> dataAll = new List<byte>(); 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); dataAll.AddRange(bs); 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; } }
最后是部分的。IRequestHandler实现代码
public class RequestHandler : IRequestHandler { // 略去代码 ... public event Action<byte[]> NotifyMsg; IResponseFilter IRequestHandler.GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) { var url = new Uri(request.Url); if (url.AbsoluteUri.Contains("https://res.wx.qq.com/zh_CN/htmledition/v2/css/base/base2e4e03.css")) { var filter = FilterManager.CreateFilter(request.Identifier.ToString()); return filter; } return null; } void filter_NotifyData(byte[] data) { if (NotifyMsg != null) { NotifyMsg(data); } } void IRequestHandler.OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) { if (request.Url.Contains("https://res.wx.qq.com/zh_CN/htmledition/v2/css/base/base2e4e03.css")) { var filter = FilterManager.GetFileter(request.Identifier.ToString()) as TestImageFilter; filter_NotifyData(filter.dataAll.ToArray()); } } }