cURLを使ってHTTPクライアントプログラムを作る | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

curlというコマンドを使えば、Linuxなどのサーバー上でコマンドラインから簡単にHTTPやFTPなどのリクエストを処理することができます。

PHPのcURL関数 は、そのcurlコマンドと同等の機能を関数レベルで提供してくれます。

このcURL関数を使って、HTTPクライアントをプログラム上に実装してみたいと思います。


ちなみに、プログラムをHTTPクライアントとして動作させたい場合は、PEARのHTTP_Client を利用した方が直感的でわかりやすいかもしれません。

PEARのHTTP_Clientの利用法は、「Twitterのタイムラインを取得するPHPスクリプト 」で簡単に触れていますので、そちらも合わせてどうぞ。



あるサイトへアクセスしてHTMLを取得する


まずは、PHPのプログラムからとあるサイトへアクセスし、ブラウザ同様にHTMLを取得すると言うことをやってみます。


<?php

$url = 'http://www.example.com/';

$ch = curl_init($url);

// HTTPヘッダを出力しない
curl_setopt($ch, CURLOPT_HEADER, FALSE);
// 返り値を文字列として受け取る
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// HTTPステータスコード400の以上の場合も何も処理しない
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);

// サイトへアクセス
$result = curl_exec($ch);

// HTTPステータスコードをチェックしエラーならエラー内容を出力
if(curl_errno($ch)) {
    echo 'Curl error: ' . curl_error($ch);
}

// セッションをクローズ
curl_close($ch);

// リクエストの内容を出力
var_dump($result);


これで、URIが正しければきちんとそのHTMLソースを受信することができます。

cURL関数は、curl_setopt関数 で細かなリクエストの処理を変えることが出来ます。

例えば、上記の場合はHTMLのみを受信しますが、HTTPヘッダも合わせて取得したければ、


// HTTPヘッダも出力
curl_setopt($ch, CURLOPT_HEADER, TRUE);


とすることで、下記のようにHTTPヘッダを受け取ることも出来ます。


string(282) "HTTP/1.1 200 OK
Date: Thu, 22 Apr 2010 15:12:41 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Thu, 22 Apr 2010 14:39:46 GMT
ETag: "1e7c03a-22-4831d080"
Accept-Ranges: bytes
Content-Length: 34
Connection: close
Content-Type: text/html

また、「CURLOPT_FAILONERROR」オプションをTRUEに設定しておかないと、400以上のHTTPステータスコードが返ってきた場合でも、そのままエラーページのHTMLを表示します

例えば、存在しないファイルへアクセスした場合、HTTPステータスコード404を返しますが、その404のエラーページのHTMLを受け取ってしまいます。



データをPOSTしてそのリクエストを受け取る


次は、とあるページにデータをPOSTし、そのリクエストを取得してみます。

POSTする先のプログラムは、下記のようにしておきます。


<?php

if ($_POST['var'] !== "") {
    echo "hello";
}


上記のプログラムへデータをPOSTするプログラムを書きます。


<?php

$url = 'http://www.examle.com/post.php';

// POSTするデータを連想配列で格納
$post_data['var'] = "hoge";

$ch = curl_init($url);
// HTTPヘッダを出力しない
curl_setopt($ch, CURLOPT_HEADER, FALSE);
// 返り値を文字列として受け取る
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// HTTPステータスコード400の以上の場合も何も処理しない
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
// POSTするデータをセット
curl_setopt($ch,CURLOPT_POSTFIELDS, $post_data);

// サイトへアクセス
$result = curl_exec($ch);

// HTTPステータスコードをチェックしエラーならエラー内容を出力
if(curl_errno($ch)) {
    echo 'Curl error: ' . curl_error($ch);
}

// セッションをクローズ
curl_close($ch);

// リクエストの内容を出力
var_dump($result);


結果は、下記のようにプログラムからの出力結果を受け取ることができます。


string(5) "hello"


POSTするデータは、連想配列にセットしておき「CURLOPT_POSTFIELDS」オプションでまとめて渡します。



Basic認証をクリアしてその先のページへアクセスする


次は、SSL環境下でBasic認証がかかっているHTMLファイルへアクセスしてみます。

SSLアクセスと、Basic認証を実行する場合のオプションがそれぞれ加わります。


<?php

$url = 'https://www.example.com';
$basic_user     = "foo";
$basic_password = "password";

$ch = curl_init($url);
// HTTPヘッダを出力しない
curl_setopt($ch, CURLOPT_HEADER, FALSE);
// SSLバージョン3を利用する
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
// 返り値を文字列として受け取る
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// サーバー証明書の検証をスキップ
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
// HTTPステータスコード400の以上の場合も何も処理しない
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
// Basic認証のユーザー名:パスワードをセット
curl_setopt($ch, CURLOPT_USERPWD, $basic_user . ":" . $basic_password);

// サイトへアクセス
$result = curl_exec($ch);

// HTTPステータスコードをチェックしエラーならエラー内容を出力
if(curl_errno($ch)) {
    echo 'Curl error: ' . curl_error($ch);
}

// セッションをクローズ
curl_close($ch);

// リクエストの内容を出力
var_dump($result);


Basic認証をクリアするために「CURLOPT_USERPWD」オプションと、SSL証明書の検証をスキップするための「CURLOPT_SSL_VERIFYPEER」「CURLOPT_SSL_VERIFYHOST」オプションをFALSEにセットしています。

curlは、対象ホストの情報とSSL証明書の情報が一致するか厳密にチェックするため、そのようなシステム構成になっていない場合は、処理をスキップさせるようにする必要があります。



おまけ: cURL関数を使ってTwitterへデータをPOSTする


Twitterへ投稿するPHPスクリプト 」では、PEARのHTTP_Clientを利用してプログラムを書いてみましたが、今回のcurlでの処理を応用して書き直して見ます。


<?php

$url = 'https://twitter.com/statuses/update.xml';
$twitter_user     = "your_twitter_id";
$twitter_password = "your_twitter_password";

// TwitterへPOSTする内容
$tweet['status'] = "hello";

$ch = curl_init($url);
// HTTPヘッダを出力しない
curl_setopt($ch, CURLOPT_HEADER, FALSE);
// SSLバージョン3を利用する
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
// 返り値を文字列として受け取る
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// サーバー証明書の検証をスキップ
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
// HTTPステータスコード400の以上の場合も何も処理しない
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
// TwitterAPIのBasic認証のユーザー名:パスワードをセット
curl_setopt($ch, CURLOPT_USERPWD, $twitter_user . ":" . $twitter_password);
// ツイートするデータをセット
curl_setopt($ch, CURLOPT_POSTFIELDS, $tweet);
// Expectヘッダを抑制
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));

// サイトへアクセス
$result = curl_exec($ch);

// HTTPステータスコードをチェックしエラーならエラー内容を出力
if(curl_errno($ch)) {
    echo 'Curl error: ' . curl_error($ch);
}

// セッションをクローズ
curl_close($ch);

// リクエストの内容を出力(XMLで返ってくるので実際は解析処理が必要)
var_dump($result);


今回はHTTPSで通信するバージョンで書いてみました。

また、今までのプログラムと比べ、「CURLOPT_HTTPHEADER」というオプションを加えています。

これを加えなかった場合、TwitterのAPIより下記のようなメッセージが返ってきます。


<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>417 Expectation Failed</title>
</head><body>
<h1>Expectation Failed</h1>
<p>The expectation given in the Expect request-header
field could not be met by this server.</p>
<p>The client sent<pre>
    Expect: 100-continue
</pre>
but we only allow the 100-continue expectation.</p>
</body></html>


クライアント側から「Expect: 100-continue」リクエストを投げろと要求してるけど、Twitterのサーバーでは許可してないよって返ってくるので、「Expect:」ヘッダを加えています

で、この「100-continue」リクエストというのは、大きなデータを投げるときに、まずはヘッダ情報だけを投げ、「100 Continue」のレスポンスが返ってきたらボディ(データ本体)を投げるというHTTP1.1の仕様のようです。


参考: Twitter APIの417 Expectation Failed対策 @ エスカフラーチェブログ


やっぱり、HTTP_Clientを使うより若干手間がかかりますね。。。