APIテスト等で活躍するcurlコマンド徹底活用術

curl-eyecatch

最近、curlを使う機会が多かったので共有したいと思います。

curlはオプションが多いのが魅力ですが、手軽に使うときは「イツメン」項目で固まる事が多いです。私がよく使ってる構成(というよりもラッパークラス)を共有します。

ソース

ソースコードで示したほうがわかりやすいと思いますので、まず最初に私が普段利用しているクラスの例をご紹介します。

<?php

class CurlWrap
{
    private $ch = null;
    private $curlOptVerbose = false;

    /**
     * インスタンス破棄時にcurlをClose
     */
    public function __destruct()
    {
        if (isset($this->ch)) {
            curl_close($this->ch);
            unset($this->ch);
        }
    }

    /**
     * Curlログ用
     * @param $curlOptVerbose
     * @return $this
     */
    public function setCurlOptVerbose($curlOptVerbose)
    {
        $this->curlOptVerbose = $curlOptVerbose;
        return $this;
    }

    /**
     * POST実行(例)
     * @param $url
     * @param $param
     * @return mixed|string
     */
    public function post($url, $param)
    {
        // POST に必要なオプションを追加して、後は通常の実行を行います。
        $curlOpts[CURLOPT_POST] = true;
        $curlOpts[CURLOPT_POSTFIELDS] = http_build_query($param);
        return $this->exec($url, $curlOpts);
    }

    /**
     * オプション設定
     * @param $url
     * @param $curlOpts
     */
    private function setCurlOpts($url, $curlOpts)
    {
        // curlコネクションを使い回す場合、ハンドラの中身が残ってしまうので、リセットをしています。
        curl_reset($this->ch);

        $curlOpts[CURLOPT_URL] = $url;

        // APIサーバ側でログを見やすくする為に、ユーザエージェントを変えてます。
        $curlOpts[CURLOPT_USERAGENT] = 'API client';

        // 自己証明書、いわゆる「オレオレ証明書」対策です。
        $curlOpts[CURLOPT_SSL_VERIFYPEER] = false;
        $curlOpts[CURLOPT_SSL_VERIFYHOST] = false;

        // ヘッダを直接出力するとパースが非常に面倒になるので、ここでは消しています。
        $curlOpts[CURLOPT_HEADER] = false;

        // CURLOPT_VERBOSE ログの保存設定。通信がおかしい?と思ったときにtrueにすると幸せになれます。
        if ($this->curlOptVerbose) {
            $fp = fopen("/some/log/file/path", 'a');
            $curlOpts[CURLOPT_STDERR] = $fp;
            $curlOpts[CURLOPT_VERBOSE] = $this->curlOptVerbose;
        }

        // これを入れないとcurl_exec()の返り値が直接出力されます。
        $curlOpts[CURLOPT_RETURNTRANSFER] = true;

        // Locationヘッダを再帰的に追うようにする設定です。つまりリダイレクト対策。
        $curlOpts[CURLOPT_FOLLOWLOCATION] = true;
        $curlOpts[CURLOPT_MAXREDIRS] = 10;

        curl_setopt_array($this->ch, $curlOpts);
    }


    /**
     * @param $url
     * @param $curlOpts
     * @return mixed|string
     */
    private function exec($url, $curlOpts)
    {
        // curlコネクションのハンドラがなければ作ります。
        if (!isset($this->ch)) {
            $this->ch = curl_init();
        }

        // オプションを追加して、
        $this->setCurlOpts($url, $curlOpts);

        // 実行します。
        $result = curl_exec($this->ch);

        // 必要ならログを保存して、
        if ($this->curlOptVerbose) {
            $this->dumpLog($result);
        }

        // エラーをチェックしたあと、
        if (curl_errno($this->ch)) {
            $result = 'Error: ' . curl_error($this->ch);
        }

        // 結果を返します。
        return $result;
    }

    /**
     * ロギング処理(一例)
     * @param $var
     * @return $this
     */
    protected function dumpLog($var)
    {
        // 「出力用バッファ」を使って、var_dump の結果をファイルに保存する例です。
        // ob_start ~ ob_end_clean の区間は、出力されるはずの内容がバッファされます。
        ob_start();

        // ここが画面に出ず、一旦バッファに留め置かれます。
        echo '[' . $this->getMicroTime() . "]\n";
        var_dump($var);
        echo "\n------------------------------\n\n";

        // ここでバッファの内容がかえります。
        $result = ob_get_contents();

        // バッファ内容を消去、バッファリングをオフにします。
        ob_end_clean();

        // 後は単純に文字列として、結果を保存すればOKです。
        $fileName = '/some/log-dir/dump.log';
        file_put_contents($fileName, $result . "\n", FILE_APPEND);

        return $this;
    }

    /**
     * マイクロ秒までの時刻
     * @return string
     */
    protected function getMicroTime()
    {
        // マイクロ秒までの時刻を取得して、マイクロ秒部分を分割します。
        $times = explode('.', microtime(true));

        // ただし、ジャストX秒だった場合マイクロ秒部分が消えるため、文字列として追加します。
        if (!isset($times[1])) {
            $times[1] = "0000";
        }

        // マイクロ秒は4桁で0埋めして、日時にドットで再度結合します。
        return date('Y-m-d H:i:s', $times[0]) . '.' . str_pad($times[1], 4, 0, STR_PAD_LEFT);
    }
}

やっていることで特殊なこと

curlコネクションの使い回し

何度もAPIをコールする事が多く、TCPコネクションを埋め尽くしそうになった事があるため、念の為こうしています。デストラクタで破棄されるまでは同じコネクションを使いまわし、都度接続しなくて済むようになっています。

普通に使う分なら要らないかもしれませんが、自社APIなどを連続で叩きまくる場合などは、こうした方がインフラチームの胃に優しくなると思います。

CURLOPT_VERBOSE の(ある程度)選択的な保存

インフラチームのメンバーに「curlの接続ログが見たい」と言われた際の経験からです。ここで吐き出されるログを見せると、問題をあっという間に解決してくれたりするので、おすすめのオプションです。

アプリケーション内で用いるbodyに混ぜ込まなくて済むので、パース処理とか考えなくて良いのが便利です。

ついでに1秒未満間隔の超高速リクエストを行う時用に、ロギングはマイクロ秒でできるようにしてあります。インフラチームの胃を痛めるので控えめにしたいですが、時には心を鬼にしなくてはならない事もあるのです(涙)。

使い方

CURLOPT_VERBOSEの有効化を含め、下記記述で使用できます。

$curl = new CurlWrap;
$result = $curl->setCurlOptVerbose(true)->post('https://example.com', ['key' => 'value']);

var_dump($result);

個人的にメソッドチェーン使った方が処理の流れを見やすいので、こういった局面ではよく使っています。後から処理順序を変える時とかは改修しやすくなる気がしています。多すぎても複雑になりますが……。

まとめ

curl自体はサーバで使える様になっていれば普遍的に使いまわせるので、こういったクラスをひとつ作っておくと便利です。このクラスも、趣味のサイト用に作ったものを、仕事に転用したものだったりします……笑

実際のところ、最近のフレームワーク等を用いた開発、特にサービス本体でcurlを生で使う事は減ってきていますが、簡単な社内テスト等ではまだまだ現役だと思います。