おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

BrowserMob Proxyを用いて認証プロキシありの環境下でSeleniumによるChrome自動操作を行う

みなさん、認証プロキシはお好きですか?

私はもちろん嫌いです。が、まあそういう環境がまだまだ多数あるということも知っておりますし諸事情を考えるとやむを得ないこともあるのかなーとは思います。

そういうところでSeleniumによるGUI自動操作を行いたいというニーズがありまして……Google先生に聞いてみるといくつか答えが得られるのです。が、仕様変更があったり条件がちょっと違ったりとかしてうまくいかない。しばらく煮詰まっていたのですが、うまくいったのでご紹介。

結論としては:

github.com

を使ってこんなコードで良さそうです*1。事前に次のような変数は初期化されているものとします。

変数名 意味
proxyServer プロキシのサーバー名
proxyPort プロキシのポート番号
username プロキシ認証ユーザー名
password プロキシ認証パスワード
// BrowserMob Proxyにて、認証プロキシにチェーンするプロキシを作成する
// 参考:
// https://github.com/lightbody/browsermob-proxy/blob/master/browsermob-core/src/test/groovy/net/lightbody/bmp/proxy/ChainedProxyAuthTest.groovy
BrowserMobProxy bmp = new BrowserMobProxyServer();
// Don't use InetSocketAddress.unresolved(), use new InetSocketAddress instead
bmp.setChainedProxy(new InetSocketAddress(proxyServer, proxyPort));
bmp.chainedProxyAuthorization(username, password, AuthType.BASIC);
bmp.setTrustAllServers(true);
bmp.start();

// 動作しているプロキシをSeleniumのProxyクラスに変換して登録
Proxy proxy = ClientUtil.createSeleniumProxy(bmp);
proxy.setNoProxy("localhost"); // Proxy除外設定もかける

DesiredCapabilities cap = new DesiredCapabilities();
cap.setCapability(CapabilityType.PROXY, proxy);

System.setProperty("webdriver.chrome.driver", "./driver/linux/chromedriver");
WebDriver driver = new ChromeDriver(cap); // 古いスタイルだというのは知っております……。

driver.get("http://www.google.com"); // サンプル

// ここにいろんな操作を書く

driver.quit();
bmp.stop();

探し方が良くないのかもしれないのですがBrowserMob ProxyのEmbeddedモード(Javaのコード内でライブラリとして利用する方法)がなんかいい情報がなくて、あーでもないこーでもないと試行錯誤していたのですが、コード片上にコメントで書いてあるとおり ChainedProxyAuthTest.groovy っていうズバリのテストコードがあって、それを参考にしました。

BrowserMobProxy.setChainedProxy() に渡すための InetSocketAddress をどうやって生成するかについては、BrowserMob Proxyの過去のIssueに答えがありました*2

枝葉ですが、私自身はSeleniumのラッパークラスであるSelenideを使ってるので、このときのプロキシ設定は DesiredCapabilities を使う代わりに WebDriverRunner.setProxy() すればうまくいきます。

試行錯誤の内容

  • 「自動テストスクリプトからは認証なしプロキシとして見えて、実際の認証プロキシにチェーンしてそちらに認証情報を渡す」小さなプロキシ(以下チェーンプロキシ)を立てるって方法が複数ヒットする。たしかにうまく行くのはわかるけど、いろいろあってすごくやりにくいのでこの方式は「どうしようもない場合の切り札」として検討後回し。
  • SeleniumProxy.setSocksUsername() / Proxy.setSocksPassword() を使えるって情報が複数ヒットするも、正しく動作せず。まあSocksって名前だしねえ……。もしかしたらプロキシ認証がBasic認証じゃない場合は通ったりするのかしら。
  • Chromeには --proxy-auth ってコマンドライン引数がある……という情報があったが、指定しても無視されるし、頼りのList of Chromium Command Line Switches にはそんなオプションはない。廃止された?
  • 環境変数 http_proxy で指定すれば……という意見もあったけど、今回、操作したい対象そのものはプロキシの中にあって、そこから呼ばれてるJSとかCSSが外部……という状態なので除外設定が必要。環境変数だとそういう制御ができない気がして、そっち側の調査はあまり深堀してない。
  • 手詰まりなのでSeleniumHQのSlackで聞いてみたところ、……Seleniumはそういう機能ないのでBrowserMob Proxyを使うといいよと助言をもらう。

f:id:naruoga:20180204222533p:plain

  • ふむふむと調べると……。テンパっていた中で超斜め読みして、うーんこれは結局チェーンプロキシを立てる話なのでは、と誤読して、ちゃんと調べるのは後回しにした。
  • 色々忙しくしてたら本件納期が差し迫っているのに解決のめど立ってなくてやばい。そんなわけでナナメ読みしたドキュメントを少し丁寧に読んだら、BrowserMob Proxyにはプロセスとして動作するモードとJava埋め込みできるモードがあって、後者ならプロセス別立て要らないのかも? しかも「Using with Selenium」なんて項目もあるし。ちょっと期待。
  • ざくっと調査すると次の記事がヒット。この記事のコード例は今のBrowserMob Proxyだとまったく通らない*3けど、やりたいことはできるようなので真面目に書き方を調べる。

www.softensity.com

  • ドキュメントを読んでもいまいち使い方わかんないしGoogleで探してもずばりのページはないなあ……というかJavaDocとかはないのか、と思って調べたけど、考えてみればソース読めばいいんだよね。ということで読む。

https://github.com/lightbody/browsermob-proxy/blob/master/browsermob-core/src/main/java/net/lightbody/bmp/BrowserMobProxy.java

  • というかソース読んでるぐらいならテスト見れば使い方わかるんじゃね? と思って、 src/test 以下を探したら正解!
  • 真面目に調べ始めてからコードをいじってあーでもないこーでもないってしてた時間はそんなには長くないかな。2〜3時間? 返す返すもテンパってたときにドキュメントをちゃんと読まなかったのが悔しい。
    • コード書くよりめんどくさかった作業は、検証用に手元に認証プロキシがある環境を作ることでした。結局 GitHub - sameersbn/docker-squid: Dockerfile to create a Docker container image for Squid proxy server こいつを使って、/etc/squid3/squid.conf はホストにマウントして認証設定書いて、/etc/squid3/.htpasswd はめんどくさかったのでコンテナ内部に直接作っちゃいましたが、これもホストにマウントすればよかったんだな。この話も気が向いたら書きます。

あとから考えるとBrowserMob ProxyをSeleniumから使うことについては:

定番のこの本に書いてあるので、もっと早く気づいてもよかった。BrowserMob Proxyはパフォーマンス測定とかにも使うやつなので、あんまり印象に残ってなかったんですよね(いいわけ)。

*1:このコード片はSelenium Grid非対応です。Grid化についてはBrowserMob ProxyドキュメントのUsing with Seleniumにちょろっと書いてあります。

*2:Javaの経験それほどちゃんとしてないので、こういうときにぱっと正しい書き方が浮かばないの困るんですよねえ。

*3:BrowserMob Proxyはバージョン2で大きく仕様変更したらしいです。