Posted at

毎回 API 叩かない中間キャッシュ API をつくる

More than 1 year has passed since last update.

たまにこんなものが欲しい時があります。


  • Web ページなどで外部 API 叩かせたい。

  • (かつ) OAuth 認証などしてもらわずに特定のデータを外部 API で取りたい。

  • (かつ) token はもちろん client に置けない。

  • (かつ) 毎度 API を叩かずに、時間が空いた場合のみ fetch したデータを返したい

つまり間に 1ホップ必要なのですが、アプリケーションとストレージが必要になります。

こんなサービスないかなと探し方もわからず見つけられてないです。

なのでとりあえず Google Apps Script での実装したものについて書きます。


具体的に何をしたいのかの例


  • Tumblr API で特定の Feed を Web に設置する。


  • Steam API で特定のデータを監視したい。


API 制限を消費したくないし、token も置けません。

=> つまりキャッシュが欲しい


Google Apps Script による実装

Google Apps Script には書いた関数を Web API 化する機能があります。そして SpreadSheet へのアクセスが出来るのでデータストアとして使えます。(API 化の手順は調べるとたくさん出てくるので見て下さい。)

Steam API で自分のユーザの persona state を取ってくるものを例に作ります。

https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0002.29


personastate

The user's current status. 0 - Offline, 1 - Online, 2 - Busy, 3 - Away, 4 - Snooze, 5 - looking to trade, 6 - looking to play.



実装内容


  • Steam の persona state を返す APIを作る

  • 取得したデータは SpreadSheet に保存(キャッシュ)しておく。

  • リクエスト時から 5分以内に更新したキャッシュがある場合はそれを返す。


コード

function doGet() {

const cell = new CellManager("A2", "B2", getSteamState)
const s = cell.getSmartValue();
return jsonResponse({ state: s, updatedAt: cell.getTime() });
}

// Steam API でユーザの状態を取る
function getSteamState() {
const url = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=xxxxxxxxxx&steamids=76561198129612698";
const response = UrlFetchApp.fetch(url);
const player = JSON.parse(response).response.players[0]
return player.personastate
}

CellManager = function(valuePos, timePos, fetchFunc) {
this.sheet = getSheet();
this.valueRow = this.sheet.getRange(valuePos);
this.timeRow = this.sheet.getRange(timePos);
this.fetchFunc = fetchFunc;
}

CellManager.prototype.getValue = function() { return this.valueRow.getValue(); }
CellManager.prototype.getTime = function() { return this.timeRow.getValue(); }

CellManager.prototype.setValue = function(v) { this.valueRow.setValue(v); }
CellManager.prototype.refreshTime = function() {
this.timeRow.setValue(new Date());
}

CellManager.prototype.fetchValue = function() {
const s = this.fetchFunc()
this.refreshTime();
this.setValue(s)
return s
}

CellManager.prototype.isOld = function() {
return this.getTime() < date5MinutesAgo();
}

// 前回のリクエストが5分以内だったら保存したデータを使う
CellManager.prototype.getSmartValue = function() {
if (!this.isOld()) {
return this.getValue();
} else {
return this.fetchValue();
}
}

// global funcs

function getSheet() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
return spreadsheet.getSheetByName('db');
}

function jsonResponse(payload) {
ContentService.createTextOutput()
var output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.JSON);
output.setContent(JSON.stringify(payload));
// return response-data
return output;
}

function date5MinutesAgo() {
const date = new Date();
date.setMinutes(date.getMinutes() - 5);
return date;
}

Cropper_and_elzup-steam.png

URL を叩くと doGet が実行されてレスポンスが帰ってきます。

{"state":0,"updatedAt":"2018-02-06T11:59:46.582Z"}

データは更新されています。

elzup-steam_-_Google_スプレッドシート.png


ちょっとコードの解説

CellManager クラスが SpreadSheet への I/O をしています。

CellManager.isOld() では記録されているタイムスタンプが 5分前より古いか確認しています。

別の API を叩きたい場合は getSteamState 関数を置き換えるだけです。

SpreadSheet の記録座標も new CellManager の引数で指定します。

date5Minutes (キャッシュ時間) のロジックも汎用にすればよかったかもしれません。


終わり

もっとスマートなやりかたとかあれば

コード処理が挟まると限られそうなのでレスポンス丸々流すのでもいいので。