HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

PHPでHTTPS接続をする

PHPから何かしらのAPIをたたいたりするときに必要じゃないか、ということで、環境作成と接続テストをしてみる。

環境構築

環境は以前に作ったものをベースにする。(Apache httpd 2.2/PHP 5.3)

(実際は、Ubuntu仮想マシン上でも構築、と思ったのだが、特に設定が必要ない状態だったので割愛)
設定自体は楽なもので、今回使用する環境ではphp.ini内の以下のコメントを外すだけ。

;extension=php_openssl.dll

あと、後述のサンプルを実行するには、URLをファイルとして開くことが許可されていることが必要らしい。

; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
; http://php.net/allow-url-fopen
allow_url_fopen = On

自分の環境ではもともとOnになっていたが、そうでない場合は適当に設定変更。
Apache経由で利用する場合は、Apacheを再起動。これで設定は完了。
phpinfo()を見てみると、以下のような記述が見つかるはず。

Registered PHP Streams => php, file, glob, data, http, ftp, zip, compress.zlib, https, ftps, phar, sqlsrv
Registered Stream Socket Transports => tcp, udp, ssl, sslv3, sslv2, tls


ちなみに、今回使用するApacheの環境では、以下のようなレスポンスを返す。

  • http://localhost/samples2/protocol.txt
    • "HTTP"
  • https://localhost/samples2/protocol.txt
    • "HTTP over SSL"

("samples2"というのに特に意味はない。)

サンプル

今回、以下の4種類の関数を使ったサンプルプログラムを作成。
(注:エラーチェックとか、HTTPへのまじめな対応とかは一切していないので(以下略))

  • file_get_contents (PHP 4.3.0以降, 5)
  • fopen (PHP 4, 5)
  • fsockopen (PHP 4, 5)
  • stream_socket_client (PHP 5)

上2つ(file_get_contents、fopen)は、プロトコルを判断して適切に処理してくれるタイプの関数なので、HTTPならばレスポンスボディのみが返される。
下2つ(fsockopen、stream_socket_client)は、トランスポート層レベルを扱うタイプの関数なので、生のHTTPリクエスト/レスポンスをプログラム側で扱う必要がある。
他にpfsockopenというのがあるらしいが、基本はfsockopenと同じなので省略。

<?php
function test_file_get_contents($url) {
	printf("=== %s ===\n", __FUNCTION__);
	var_dump(file_get_contents($url));
}
function test_fopen($url) {
	printf("=== %s ===\n", __FUNCTION__);
	$sock = fopen($url, 'rb');
	if ($sock) {
		while (($s = fgets($sock))) {
			var_dump($s);
		}

		fclose($sock);
	}
}

function test_fsockopen($host, $port, $path) {
	printf("=== %s ===\n", __FUNCTION__);
	$sock = fsockopen($host, $port);
	if ($sock) {
		fputs($sock, "GET {$path} HTTP/1.1\r\n");
		fputs($sock, "Host: localhost\r\n");
		fputs($sock, "\r\n");

		$len = 0;
		while (strlen(trim($s = fgets($sock)))) {
			if (preg_match("/^Content-Length: /", $s)) {
				$len = (int)preg_replace("/^Content-Length: /", '', $s);
			}
			echo '[H]' . $s;
		}
		$s = fread($sock, $len);
		var_dump($s);

		fclose($sock);
	}
}
function test_stream_socket_client($remote_socket, $path) {
	printf("=== %s ===\n", __FUNCTION__);
	$sock = stream_socket_client($remote_socket);
	if ($sock) {
		fputs($sock, "GET {$path} HTTP/1.1\r\n");
		fputs($sock, "Host: localhost\r\n");
		fputs($sock, "\r\n");

		$len = 0;
		while (strlen(trim($s = fgets($sock)))) {
			if (preg_match("/^Content-Length: /", $s)) {
				$len = (int)preg_replace("/^Content-Length: /", '', $s);
			}
			echo '[H]' . $s;
		}
		$s = fread($sock, $len);
		var_dump($s);

		fclose($sock);
	}
}

test_file_get_contents('http://localhost/samples2/protocol.txt');
test_file_get_contents('https://localhost/samples2/protocol.txt');
echo "\n";
test_fopen('http://localhost/samples2/protocol.txt');
test_fopen('https://localhost/samples2/protocol.txt');
echo "\n";
test_fsockopen('localhost', 80, '/samples2/protocol.txt');
test_fsockopen('ssl://localhost', 443, '/samples2/protocol.txt');
echo "\n";
test_stream_socket_client('tcp://localhost:80', '/samples2/protocol.txt');
test_stream_socket_client('ssl://localhost:443', '/samples2/protocol.txt');
?>

比較のために、HTTPで通信する場合の処理も一緒に入れてみた。
これを実行すると、以下のような出力が得られる。

=== test_file_get_contents ===
string(4) "HTTP"
=== test_file_get_contents ===
string(13) "HTTP over SSL"

=== test_fopen ===
string(4) "HTTP"
=== test_fopen ===
string(13) "HTTP over SSL"

=== test_fsockopen ===
[H]HTTP/1.1 200 OK
[H]Date: Sat, 24 Dec 2011 19:19:14 GMT
[H]Server: Apache/2.2.16 (Win32) mod_ssl/2.2.16 OpenSSL/0.9.8o PHP/5.3.3 DAV/2
[H]Last-Modified: Sat, 24 Dec 2011 18:46:38 GMT
[H]ETag: "400000009a523-4-4b4daf41d0f50"
[H]Accept-Ranges: bytes
[H]Content-Length: 4
[H]Content-Type: text/plain
string(4) "HTTP"
=== test_fsockopen ===
[H]HTTP/1.1 200 OK
[H]Date: Sat, 24 Dec 2011 19:19:14 GMT
[H]Server: Apache/2.2.16 (Win32) mod_ssl/2.2.16 OpenSSL/0.9.8o PHP/5.3.3 DAV/2
[H]Last-Modified: Sat, 24 Dec 2011 18:46:58 GMT
[H]ETag: "500000009a522-d-4b4daf54f0770"
[H]Accept-Ranges: bytes
[H]Content-Length: 13
[H]Content-Type: text/plain
string(13) "HTTP over SSL"

=== test_stream_socket_client ===
[H]HTTP/1.1 200 OK
[H]Date: Sat, 24 Dec 2011 19:19:14 GMT
[H]Server: Apache/2.2.16 (Win32) mod_ssl/2.2.16 OpenSSL/0.9.8o PHP/5.3.3 DAV/2
[H]Last-Modified: Sat, 24 Dec 2011 18:46:38 GMT
[H]ETag: "400000009a523-4-4b4daf41d0f50"
[H]Accept-Ranges: bytes
[H]Content-Length: 4
[H]Content-Type: text/plain
string(4) "HTTP"
=== test_stream_socket_client ===
[H]HTTP/1.1 200 OK
[H]Date: Sat, 24 Dec 2011 19:19:15 GMT
[H]Server: Apache/2.2.16 (Win32) mod_ssl/2.2.16 OpenSSL/0.9.8o PHP/5.3.3 DAV/2
[H]Last-Modified: Sat, 24 Dec 2011 18:46:58 GMT
[H]ETag: "500000009a522-d-4b4daf54f0770"
[H]Accept-Ranges: bytes
[H]Content-Length: 13
[H]Content-Type: text/plain
string(13) "HTTP over SSL"

単純なGETだけなら上記のどれでもよいが、POSTなどが絡んでくると下2つを使わざるを得ない。(2021/05/30 ウソウソ。上2つでもPOSTはできる。hhelibex.hatenablog.jp)が、SSLの話とは関係なくなるので、ここでは触れない。

参考