browsermob-proxy, 基于Java的代理服务

来源:互联网 发布:智能网络电视机论坛 编辑:程序博客网 时间:2024/05/18 00:48
1:基础介绍
    https://github.com/lightbody/browsermob-proxy
    browsermob-proxy 以下在文章简称BMP。
    BMP的具体流程有点类似与Flidder或Charles。即开启一个端口并作为一个标准代理存在,当HTTP客户端(浏览器等)设置了这个代理,则抓取并有能力修改所有的请求细节并获取返回内容。
    BMP可以将HTTP请求细节数据导出到HAR文件
HAR(HTTP档案规范),是一个用来储存HTTP请求/响应信息的通用文件格式,基于JSON。这种格式的数据可以使HTTP监测工具以一种通用的格式导出所收集的数据,这些数据可以被其他支持HAR的HTTP分析工具(包括Firebug、httpwatch、Fiddler等)所使用,来分析网站的性能瓶颈。
http://horve.github.io/2015/09/08/har-detail/

    BMP是基于LittleProxy,而LittleProxy又是基于Netty。
    BMP有两种模式,嵌入式模式是利用Java代码来启动代理,并通过Java代码来截取修改请求获取内容。另一种是独立启动模式,可以通过命令行来启动,通过RestAPI来进行操作(https://github.com/lightbody/browsermob-proxy#rest-api)。
    本文只讨论嵌入式模式。嵌入式模式可以跟Selenium很好的集成起来。


2:环境搭建
 <dependency>
    <groupId>net.lightbody.bmp</groupId>
    <artifactId>browsermob-core</artifactId>
    <version>2.1.4</version>
</dependency>

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>21.0</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
在resources目录下加入log4j.properties

log4j.rootLogger = INFO, stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d %p [%c] - %m%n

注:
一定要配置好日志环境,否则系统出错了完全看不出来。血泪教训。

在我配置时候,BMP2.1.4 和 Guava22.0 是不兼容的,导致服务起来之后,浏览器始终无法打开页面。然而,没有任何迹象表现出代理没有起来,或者运行过程中有错。

多方Google无果,因为完全没有日志记录。只能搜索 “browsermob not working” 或者 “browsermob connection reset”等等毫无意义的现象。然而什么都没有搜出来。

最后,配好了日志环境,发现了错误。
java.lang.NoSuchMethodError:com.google.common.net.HostAndPort.getHostText()
经过搜索发现,实际上是由于BMP官方兼容性问题导致。
https://github.com/lightbody/browsermob-proxy/issues/638
BrowserMobProxy proxy = 
new BrowserMobProxyServer();
proxy.start(
  8180, InetAddress.getByName("localhost")
);
// 不要用常见的 8888 被 Charles用啦

Thread.sleep(10000000);
proxy.stop();
这样就算起了好了一个代理服务器,端口是8888。
是不是非常简单。

代理启动之后,主线程可以Sleep一段时间,然后把代理关掉,这样就能够实现定时关闭代理的功能了。

实际上,还可以拦截请求,在某些请求完成之后关闭代理。

由于代理是单独线程,跟启动它的主线程没有关系,因此主线程Sleep不会影响代理的可用性。


    注:为了测试代理的运行情况, 可以将log4j.rootLogger改为DEBUG模式,这样会默认输出每个请求的细节。
        Firefox推荐使用ProxySwitcher, Chrome推荐SwitchyOmega来进行代理设置。

3:SSL配置
    默认不配置的情况下,对于非SSL的页面是可以随便访问的。但是对于SSL站点,会出现不是私密连接的告警,甚至直接打不开。
    
    通过官方文档,我们知道需要安装一个证书。 
    
    https://github.com/lightbody/browsermob-proxy/blob/master/browsermob-core/src/main/resources/sslSupport/ca-certificate-rsa.cer
    下载这个文件,存到系统中。在Mac下是打开“钥匙串访问”,将这个文件拖入其中。
     
    然后双击打开证书详情,并选择始终信任。
    
    然后就可以直接访问SSL站点了。不过这个证书用作测试可以的,长期信任并不是一个好做法。这是一个公开证书,很容易被他人盗用。所以比较合理的方式是生成自己的证书。有兴趣自己研究,链接在这里。找时间单独写SSL相关的原理。https://github.com/lightbody/browsermob-proxy/blob/master/mitm/README.md

    但是,在Mac上,这个方法对Firefox是无效的。Firefox自己维护了一系列证书。所以需要单独为其导入。这个特性其实很有用,即单独在FF上信任证书,测试的时候用FF,全局则可以不信任这个测试证书。然而,54.0 (64 位)版本的FF,即便加入了并信任了该证书,依旧无法访问SSL。为此,暂时没有Google到办法。只能弃用了。
    不管怎么说,导入的办法如下:
    首选项 -> 高级 -> 证书 -> 查看证书 -> 导入,选中证书文件之后,选择下面的三个信任,然后确定。

4:拦截请求
BrowserMobProxy proxy = new BrowserMobProxyServer();
proxy.start(8100, InetAddress.getByName("localhost")); // 不要用常见的 8888 被 Charles用啦
RequestFilter

proxy.addRequestFilter((request, contents, messageInfo) -> {
    if (request.getMethod().equals(HttpMethod.POST)) {
        System.out.println(request.getUri() + " ######### " + contents.getTextContents());
    }
    System.out.println(request.getUri() + " --->> " + request.headers().get("Cookie"));
    return null;
});    

RequestFilter 是一个接口,只有一个方法 
HttpResponse filterRequest(HttpRequest request, HttpMessageContents contents, HttpMessageInfo messageInfo);

当这个方法在Proxy中被调用的时候,request参数包括了HTTP method, URI, headers等等。这些都是可以修改的。

当POST方法等提请求带有参数的时候,content中可以取到参数详情。content可以通过
HttpMessageContents#setTextContents(String) 或者 HttpMessageContents#setBinaryContents(byte[]) 来进行修改。

对于 request 和 contents 都会直接反映在最终给服务器的请求上。

如果返回值不是null, 那么代理不再往外发送请求,而是直接将这个非空的元素返回去给浏览器。
ResponseFilter
proxy.addResponseFilter((response, contents, messageInfo) -> {
    System.out.println(
        messageInfo.getUrl() + " >>>>>> " + response.getStatus() + " : " +
        response.headers().get("cookie") + " | " + contents.getTextContents()
    );
});
ResponseFilter是一个接口,只有一个方法 。
void filterResponse(HttpResponse response, HttpMessageContents contents, HttpMessageInfo messageInfo);

当这个方法在Proxy中被调用的时候,response参数包括了URI, headers, status line等等。

contents是返回的真正内容,可以通过以下方法来进行修改。
HttpMessageContents#setTextContents(String) 或者 HttpMessageContents#setBinaryContents(byte[]) 

对response和content的修改,都会最终反映到请求发起方。
存储HAR文件
// create a new HAR with label
proxy.newHar("bmp”);
// enable more detailed HAR capture, if desired (see CaptureType for the complete list)
proxy.enableHarCaptureTypes(
    CaptureType.REQUEST_HEADERS, CaptureType.REQUEST_CONTENT, CaptureType.REQUEST_BINARY_CONTENT, CaptureType.REQUEST_COOKIES,    CaptureType.RESPONSE_HEADERS, CaptureType.RESPONSE_CONTENT, CaptureType.RESPONSE_BINARY_CONTENT, CaptureType.RESPONSE_COOKIES);

// 手动输入验证码,然后继续爬取过程
while (true) {
    Scanner scanner = new Scanner(System.in);
    String cmd = scanner.next();
    if(cmd.equals("quit")){
        break;
    }
}

try {
    Har har = proxy.getHar();
    File harFile = new File("/Users/wangqi/Desktop/bmp.har");
    har.writeTo(harFile);
} catch (IOException ioe) {
    ioe.printStackTrace();
}

proxy.stop();

http://www.swtestacademy.com/webdriver-browsermob-proxy/
在线工具http://www.softwareishard.com/ 或者 Charles 可以打开存储下来的HAR文件。
由于HAR需要用Charles打开,而Charles默认的代理端口是8888,当Charles开启,所以如果BMP使用8888,可能出现一只拿不到任何HAR的诡异情况。
而,实际上,启动过程中竟然么有报端口被占用,也真是诡异。
BMP会自动加上一个RequestHeader,为了减少服务器对代理的影响,需要删除该Header信息。
https://github.com/lightbody/browsermob-proxy/issues/213
        proxy.addLastHttpFilterFactory(new HttpFiltersSourceAdapter() {
            @Override
            public HttpFilters filterRequest(HttpRequest originalRequest) {
                return new HttpFiltersAdapter(originalRequest) {
                    @Override
                    public HttpResponse proxyToServerRequest(HttpObject httpObject) {
                        if (httpObject instanceof HttpRequest) {
                            ((HttpRequest) httpObject).headers().remove(HttpHeaders.Names.VIA);
                        }
                        return null;
                    }
                };
            }
        });  



5: Selenium WebDriver和BMP的联合使用
    WebDriver常用与自动化测试,所以在测试过程中将每个请求细节都完整记录下来当然是极好的。因此可以将 WebDriver和BMP联合使用,存储成HAR格式(第一部分有说明)。
    // start the proxy
    BrowserMobProxy proxy = new BrowserMobProxyServer();
    proxy.start(0);

    // get the Selenium proxy object
    Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy);

    // configure it as a desired capability
    DesiredCapabilities capabilities = new DesiredCapabilities();
    capabilities.setCapability(CapabilityType.PROXY, seleniumProxy);

    // start the browser up
    WebDriver driver = new FirefoxDriver(capabilities);

    // enable more detailed HAR capture, if desired (see CaptureType for the complete list)
    proxy.enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT);

    // create a new HAR with the label "yahoo.com"
    proxy.newHar("yahoo.com");

    // open yahoo.com
    driver.get("http://yahoo.com");

    // get the HAR data
    Har har = proxy.getHar();
原创粉丝点击