node.jsでHTTPプロキシ経由でhttpsアクセスするには
次男が生まれました!
こんにちは@kjunichiです。先月末に次男が生まれました。長男と同様に立ち会い出産で臨んだのですが、緊急で帝王切開となりバタバタしてました。自分が仮死状態で生まれたこともあり、手術室へ移動して閉めだされてから次男に会うまでは心配でした。今は母子ともに元気に退院して来てます。
プロキシを通す必要がある環境下でnode.jsでhttpsアクセスするには
普通にhttpsモジュールは使えないようなので、調べた結果のメモ。
そもそも、HTTPプロキシ経由でhttpsアクセスするには
CONNECT ssl.example.com:443 HTTP/1.1
Host: 127.0.0.1
とHTTPプロキシにこれからhttpsで通信する旨を通知する必要がある。
その後、HTTPプロキシから200の応答をもらうことで、 「以降の通信を暗号化」して行うことになる。
以降の通信を暗号化が問題だった。
PHPでは、ソケット(ストリーム)を引数に
stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
なる便利な関数が用意されているのだった。
じゃぁ、node.jsでは?
という事で、初めは、githubのnode.jsのhttpsモジュールのソースから読み出し、 接続済みのソケットをゴニョゴニョすれば力技で行けそうな気配だったが。。。
starttlsなるモジュールがあった!
npm install starttls
最初にGoogleで発見したのはgistのコードの断片だったが、その関数が starttlsなる名前であり、node.js + starttls で再度検索したらnpmに登録されていた次第。
サンプルコード
HTTP1.1でKeep Aliveな通信にしてしまっているので、 HTTPのパース処理等やっつけで、サンプルとして最低限成り立つように 試みてはいます。。
// Https access with Http Proxy Server // プロキシサーバ経由でhttps通信する var net = require('net'); var url = require('url'); var startTls = require('starttls').startTls; var HTTP_PROXY_HOST = "localhost"; var HTTP_PROXY_PORT = "8080"; var targetUrl = "https://www.google.co.jp/"; var parsedUrl = url.parse(targetUrl); var targetHost = parsedUrl.host; var port = 443; var conn = net.createConnection(HTTP_PROXY_PORT,HTTP_PROXY_HOST); conn.on('error',function(error) { console.log("error! : "+error); }); conn.on('connect',function() { console.log("connected."); // CRLFCRLFが必要 conn.write("CONNECT "+targetHost+":"+port+" HTTP/1.0\r\nHost:"+targetHost+"\r\n\r\n",function(){ var isUpgrade = false; conn.on('data',function(data) { // console.log("recieve data."); console.log(data.toString()); if(!isUpgrade) { var securePair = startTls(conn,function(){ console.log("starttls: done"); securePair.cleartext.write("GET " + targetUrl + " HTTP/1.1\r\nHost:"+targetHost+"\r\n\r\n",function() { console.log("GET https"); }); // バッファを蓄えておく配列 var bufs = []; // 受け取ったバッファの合計サイズ bufs.totalLength = 0; securePair.cleartext.on('data',function(chunk) { console.log("recieve data."); bufs.push(chunk); bufs.totalLength += chunk.length; // 現在のバッファを元にHTTPレスポンスを解析して判定 if(parse(bufs)) { console.log("Parse done: "+ Buffer.concat(bufs, bufs.totalLength).toString()); conn.end(); } }); //conn.end(); isUpgrade=true; }); } }); console.log("SSL! CONN: "); conn.on('end',function() { console.log("end"); }); }); }); // // 以下はhttpsをプロキシ経由でアクセスする事とは本質的でない部分 // function getHttpResHeaders(data) { var pos = data.toString().indexOf("\r\n\r\n"); if(pos>0) { console.log("HTTP Hedars: "+ data.slice(0,pos-1)); return data.slice(0,pos-1); } return ""; } function getLength(headers) { var targetKey = "Content-Length:"; var pos = headers.toString().indexOf(targetKey); pos = pos + targetKey.length; var tmp = headers.slice(pos).toString(); //console.log("tmp = "+tmp); pos = tmp.indexOf("\r\n"); if(pos>0) { return tmp.slice(0,pos-1); } return tmp; } // HTTPレスポンスの解析 // HTTP1.1でKeep AliveだとonEndで判定できないから頑張る function parse(bufs) { var data = Buffer.concat(bufs, bufs.totalLength); var httpHeaders=getHttpResHeaders(data); if(httpHeaders.length>0) { //console.log("HTTP Response Header!"); // Content-Lengthから受信予定のサイズを取得する。 var length = getLength(httpHeaders); //console.log("Content-Length : " + length); // Content-Length分取得済みか? var body=data.toString().slice(data.toString().indexOf("\r\n\r\n")); if(body.length >= length) { // Body部取得完了 //console.log("body = "+body); return true; } } return false; }
大変参考になったページ
関連するかもなブログ記事
- node.jsでhttpsも使えるHttp proxyを書いている
- node.jsで大きな数を扱う
- MavericksでNodObjCを試すには
- Node.jsでデバッガを使う際に必要なたった2つのこと
- [soloved]Mavericksでnode-webclがビルドできた
- Node.jsでコマンドラインアプリを書く JSONファイル編
- Safariのリーディングリストをkindleに送る
- Herokuではnode.jsで作ったHttp Proxyが動かせなかった
- UbuntuやRaspberry PiでCloud9のソースを動かすには
- cloud9 IDEのコンパイルが出来なくなった場合の対処
- iPhone4Sの疑似テザリングツールをGitHubに上げた
- node.jsで顔検出してみた
- node.jsが80番ポートで動かない #windows