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

# プロキシを通す必要がある環境下でnode.jsでhttpsアクセスするには
普通にhttpsモジュールは使えないようなので、調べた結果のメモ。

# そもそも、HTTPプロキシ経由でhttpsアクセスするには

```text:
CONNECT ssl.example.com:443 HTTP/1.1
Host: 127.0.0.1

```
とHTTPプロキシにこれからhttpsで通信する旨を通知する必要がある。

その後、HTTPプロキシから200の応答をもらうことで、
「以降の通信を暗号化」して行うことになる。

## 以降の通信を暗号化が問題だった。
PHPでは、ソケット（ストリーム）を引数に

```php:
stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
```

なる便利な関数が用意されているのだった。

## じゃぁ、node.jsでは？
という事で、初めは、githubのnode.jsのhttpsモジュールのソースから読み出し、
接続済みのソケットをゴニョゴニョすれば力技で行けそうな気配だったが。。。

## starttlsなるモジュールがあった！

```shell-session:
npm install starttls
```
最初にGoogleで発見したのはgistのコードの断片だったが、その関数が
starttlsなる名前であり、node.js + starttls　で再度検索したらnpmに登録されていた次第。

## サンプルコード
HTTP1.1でKeep Aliveな通信にしてしまっているので、
HTTPのパース処理等やっつけで、サンプルとして最低限成り立つように
試みてはいます。。

```js:
// 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;
}
```

#　大変参考になったページ
- [PROXY経由でHTTPSにアクセスする方法](http://pe5974.sakura.ne.jp/contents/proxy-https.php)

# 関連するかもなブログ記事
- [node.jsでhttpsも使えるHttp proxyを書いている](https://abrakatabura.hatenablog.com/entry/2012/04/19/231210)

# 実はもっとスマートに出来る方法を教わった
- [node.jsでHTTPプロキシ経由でhttpsアクセスを更に簡単にするには](https://abrakatabura.hatenablog.com/entry/2013/06/25/063157)