LoginSignup
12
11

More than 3 years have passed since last update.

node.jsでHTTPプロキシ経由でhttpsアクセスするには

Last updated at Posted at 2013-06-17

プロキシを通す必要がある環境下で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;
}

 大変参考になったページ

関連するかもなブログ記事

実はもっとスマートに出来る方法を教わった

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11