Skip to content

Instantly share code, notes, and snippets.

@kjunichi
Created June 18, 2013 02:15
Show Gist options
  • Save kjunichi/5802146 to your computer and use it in GitHub Desktop.
Save kjunichi/5802146 to your computer and use it in GitHub Desktop.

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

# 大変参考になったページ

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment