PhantomJS でネットワークのデバッグと SSL handshake failed
PhantomJS で itunes の url に接続しようとするとエラーになり、デバッグしたいので方法を調べた話。
まず、SSL の通信なので、別のプロセスから tcpdump などで通信をキャプチャすることは難しい。
方法の一つとして、--debug=yes
というコマンドラインオプションを渡すと、詳細なログをだしてくれるようだ。(ドキュメント には載っていないオプションだった)
$ phantomjs --debug=yes crawl.js
2014-11-01T15:53:28 [DEBUG] CookieJar - Created but will not store cookies (use option '--cookies-file=' to enable persisten cookie storage)
2014-11-01T15:53:28 [DEBUG] Phantom - execute: Configuration
2014-11-01T15:53:28 [DEBUG] 0 objectName : ""
...
あるいは面倒だけど、onResourceRequested
などのイベントを片っ端から Listen して自分でログに落とすこともできる。
こんな感じ:
var page = new WebPage(),
system = require('system');
page.onResourceRequested = function (requestData, networkRequest) {
system.stderr.writeLine('[onResourceRequested]');
system.stderr.writeLine('Request (#' + requestData.id + '): ' + JSON.stringify(requestData));
};
page.onResourceReceived = function(response) {
system.stderr.writeLine('[onResourceReceived]');
system.stderr.writeLine('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response));
};
page.onLoadStarted = function() {
var currentUrl = page.evaluate(function() {
return window.location.href;
});
system.stderr.writeLine('Current page ' + currentUrl + ' will gone...');
system.stderr.writeLine('Now loading a new page...');
};
page.onLoadFinished = function(status) {
system.stderr.writeLine('[onLoadFinished]');
system.stderr.writeLine('Status: ' + status);
};
page.onNavigationRequested = function(url, type, willNavigate, main) {
system.stderr.writeLine('[onNavigationRequested]');
system.stderr.writeLine('Trying to navigate to: ' + url);
system.stderr.writeLine('Caused by: ' + type);
system.stderr.writeLine('Will actually navigate: ' + willNavigate);
system.stderr.writeLine('Sent from the page\'s main frame: ' + main);
};
page.onResourceTimeout = function(request) {
system.stderr.writeLine('[onResourceTimeout]');
console.log('Response (#' + request.id + '): ' + JSON.stringify(request));
};
page.onResourceError = function(resourceError) {
system.stderr.writeLine('[onResourceError]');
system.stderr.writeLine('Unable to load resource (#' + resourceError.id + 'URL:' + resourceError.url + ')');
system.stderr.writeLine('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
};
page.onError = function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
system.stderr.writeLine('[onError]');
system.stderr.writeLine(msgStack.join('\n'));
};
page.open('https://itunes.apple.com/en/app/instagram/id389801252?mt=8', function (status) {
console.log('Finished');
});
ちゃんと調べていないが、CasperJS や Nightmarejs で同等のことをやろうとすると、後者じゃないとだめかもしれない。
SSL handshake failed
ちなみに itunes の url に接続できなかったのは SSL Protocol が原因だった。上記のスクリプトを実行するとこんなメッセージが出る。
2014-11-01T15:59:40 [DEBUG] Network - Resource request error: 6 ( "SSL handshake failed" ) URL: "https://itunes.apple.com/en/app/instagram/id389801252?mt=8"
または
[onResourceError]
Unable to load resource (#1URL:https://itunes.apple.com/en/app/instagram/id389801252?mt=8)
Error code: 6. Description: SSL handshake failed
curl でアクセスしてみると TLS 1.2 を使っているのがわかる
$ curl -v 'https://itunes.apple.com/en/app/instagram/id389801252?mt=8'
...
* TLS 1.2 connection using TLS_RSA_WITH_AES_256_CBC_SHA
* Server certificate: itunes.apple.com
...
で、PhantomJS のデフォルトのプロトコルは SSLv3 らしい。
Command Line Interface | PhantomJS
--ssl-protocol=tlsv1
(または any
) とオプション指定してあげると解決。
$ phantomjs --ssl-protocol=tlsv1 crawl.js
これも、CasperJS や Nightmarejs でも同様だと思う。
もちろんブラウザでも、curl でも問題ないのに PhantomJS でだけ接続できないし、エラーメッセージもほぼ無いしで結構はまった。
ちなみに POODLE 問題をうけて、1.9.8 以降はデフォルトが TLSv1 に変更になった ようだ。