PROXY経由でHTTPSにアクセスする方法

PHPからHTTPリクエストをする方法

PROXYを経由せず、普通にリクエストを飛ばす方法から記載します。

<?php

$sock = stream_socket_client('tcp://www.example.com', $errno, $errstr, 10, STREAM_CLIENT_CONNECT);

if(!$sock) exit();

$request = array();
$request[] = 'GET / HTTP/1.1';
$request[] = 'Host: www.example.com';
$request[] = 'User-Agent: TestClient';

if(!fwrite($sock, implode("\r\n", $request)."\r\n\r\n")){
	exit();
}

$response = '';
while(!feof($sock)){
	$response .= fgets($sock, 4096);
}

echo $response;

fclose($sock);

?>

と詳しい説明は省きますが、stream_socket_client(又はfsockopen)にてソケット開いて、fwriteでHTTPヘッダを書き込んで、fgetsでレスポンスを取得します。

PROXY経由でリクエストする場合

次に、PROXYを経由し、HTTPにリクエストをする場合。
検証には、solipoを使用しました。

<?php

$sock = stream_socket_client('tcp://127.0.0.1:8123', $errno, $errstr, 10, STREAM_CLIENT_CONNECT);

if(!$sock) exit();

$request = array();
$request[] = 'GET http://www.example.com HTTP/1.1';
$request[] = 'Host: www.example.com';
$request[] = 'User-Agent: TestClient';

if(!fwrite($sock, implode("\r\n", $request)."\r\n\r\n")){
	exit();
}

$response = '';
while(!feof($sock)){
	$response .= fgets($sock, 4096);
}

echo $response;

fclose($sock);

?>

違いは、stream_socket_clientのあて先をPROXYサーバにし、送るHTTPヘッダのリクエスト行を接続先を絶対パスで記述するというところです。

PROXY経由でHTTPSにリクエストする場合

本題です。
この方法は結局散々探し回った挙句、Zend_Http_Clientのソースを参考にして分かった方法です。

<?php

$sock = stream_socket_client('tcp://127.0.0.1:8123', $errno, $errstr, 10, STREAM_CLIENT_CONNECT);

if(!$sock) exit();

/*==============================================================================
	まず初めに、CONNECTメソッドでリクエストをします。
==============================================================================*/
$request = array();
$request[] = 'CONNECT ssl.example.com:443 HTTP/1.1'; //ポート番号も指定する。
$request[] = 'Host: 127.0.0.1';                      //ホストはPROXYサーバを指定
$request[] = 'User-Agent: TestClient';               //これは無くても良いですが、PROXYサーバによっては必要になる?

if(!fwrite($sock, implode("\r\n", $request)."\r\n\r\n")){
	exit();
}

/*==============================================================================
	ここでレスポンスを受け取る。コレを省略すると以降の処理でエラーとなってしまいます。
==============================================================================*/
$response01 = '';
while(!feof($sock)){
	$response01 .= fgets($sock, 4096);
	if(preg_match('/^http/i', $response01)) break; //HTTPのレスポンスが出てきたら強制的に抜けないとタイムアウトするまで空行取得し続けるようです。
}

/*==============================================================================
	上記 $response01 で、ステータスコードが200を受け取ることが出来たら(判定処理は省略します。)
	下記、stream_socket_enable_crypto関数にて、通信の暗号化をします。
==============================================================================*/
stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); //第三引数は必要に応じて変更します。

/*
Zend_Http_Clientでは、下記のように記述してありました。

$modes = array(
	STREAM_CRYPTO_METHOD_TLS_CLIENT,
	STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
	STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
	STREAM_CRYPTO_METHOD_SSLv2_CLIENT
);

$success = false;
foreach($modes as $mode) {
	$success = stream_socket_enable_crypto($sock, true, $mode);
	if ($success) break;
}

*/

/*==============================================================================
	準備が整ったので、HTTPヘッダを送信します。
==============================================================================*/
$request = array();
$request[] = 'GET https://ssl.example.com/ HTTP/1.1';
$request[] = 'Host: ssl.example.com';

if(!fwrite($sock, implode("\r\n", $request)."\r\n\r\n")){
	exit();
}

$response02 = '';
while(!feof($sock)){
	$response02 .= fgets($sock, 4096);
}

echo $response02;

fclose($sock);

?>

結局、CONNECTメソッドでトンネリングして、成功したら通信内容を暗号化する設定を行って、HTTP通信を行う、という流れになります。