GuzzleHttpでローカルキャッシュを使う

2022年2月10日

Amazon PA-APIで提供されているライブラリではGuzzleHttpが使われています。
私はAPI関連を使うときはリクエスト回数制限を気にしないでデバッグするためにローカルキャッシュをよく使うのですが、GuzzleHttpでどうやるか、しばらくの間分からずにいました。
しばらくパッチを当ててレスポンスを保存するくらいはなんとかやっていたのですが、機会があったので調べてみました。

guzzle-cache-middlewareを使います。

$ composer require kevinrob/guzzle-cache-middleware

2022/2/10追記:ver4.0だとエラーになるかもしれません。ver3.5で確認。

$ composer require "kevinrob/guzzle-cache-middleware=~3.5"

flysystemも入れますが、こちらは1.xを明示的に入れないと一部クラスが廃止されていてエラーになります。

$ composer require "league/flysystem=~1.0"

基本的には以下のような使い方になります。
GreedyCacheStrategyは指定時間の間ローカルキャッシュを参照するクラスです。(他にもいろいろあります)
この書き方ではGETメソッドのみURLを基準に180秒キャッシュされます。
(おそらくQueryStringは考慮されない)

// 2022/2/10追記:useが抜けていたのを修正
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use League\Flysystem\Adapter\Local;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
use Kevinrob\GuzzleCache\Storage\FlysystemStorage;

//......

$stack = HandlerStack::create();
$stack->push(
    new CacheMiddleware(
        new GreedyCacheStrategy(
            new FlysystemStorage(
                new Local("/tmp/foo")
            ),
            180
        )
    ),
    "cache"
); 

//......

$client = new Client([
    "handler" => $stack,
]);

取得するパラメータを元にキャッシュキーを決めたいので、カスタマイズする必要があります。
私はGreedyCacheStrategyクラスをコピーしてカスタマイズしてみました。
他に良い方法があるかもしれませんが、判明した時には訂正します。(識者の方にご助言いただけると嬉しいです。)
MyCacheStrategyという名前にして改造します。
JSON形式でPOSTするので、そのデータを横取りしデコードして必要なパラメータをキャッシュキーのハッシュ化元に追加しています。
ここら辺を必要に応じて変えれば良いと思います。

class MyCacheStrategy extends PrivateCacheStrategy
{
    //.....
    protected function getCacheKey(RequestInterface $request, KeyValueHttpHeader $varyHeaders = null)
    {
        $stream = $request->getBody();
        $pos = $stream->tell();
        $stream->rewind();
        $request_param = json_decode($stream->getContents());
        $stream->seek($pos);
        $request_param_key =
            $request_param->Keywords.
            $request_param->SearchIndex.
            $request_param->BrowseNodeId.
            $request_param->Availability.
            $request_param->SortBy.
            $request_param->ItemCount.
            $request_param->ItemPage;

        if (null === $varyHeaders || $varyHeaders->isEmpty()) {
            return hash(
                'sha256',
                'greedy'.$request->getMethod().$request->getUri().$request_param_key
            );
        }

PA-APIはPOSTなので、これも対応が必要です。
CacheMiddlewareのインスタンスにキャッシュしたいメソッドをsetHttpMethodsメソッドで指定します。

$stack = HandlerStack::create(); 

$cache_middleware = new CacheMiddleware(
    new MyCacheStrategy(
        new FlysystemStorage(
            new Local("/tmp/foo")
        ),
        180
    )
);
$cache_middleware->setHttpMethods(array('GET' => true,'POST' => true));
$stack->push(
    $cache_middleware,
    "cache"
);
//.....
$client = new Client([
    "handler" => $stack,
]);

2022/2/10追記:キャッシュヒットしたかどうかは以下のように確認できます。

$request = new Request('GET', 'https://www.example.com/');
$response = $Client->send($request);
$headers = $response->getHeaders();
if ($headers['X-Kevinrob-Cache'][0] == 'HIT') { // or 'MISS'
    // cache hit!
}

// PA-API ライブラリの場合はこちら
list($searchItemsResponse, $statusCode, $headers) = $apiInstance->getItemsWithHttpInfo( $getItemsRequest );
// or searchItemsWithHttpInfo()
if ($headers['X-Kevinrob-Cache'][0] == 'HIT') { // or 'MISS'
    // cache hit!
}

これでひとまずやりたいことができました。

PHP

Posted by kisara