Corredor

ウェブ、プログラミングの勉強メモ。

JavaScript 関数を AJAX で仕入れて実行する。関数の API 化というアイデア

最近、サーバレスアーキテクチャの一環で、FaaS (Function as a Service) とかいって、特定の関数を必要な時にだけ WebAPI 経由で実行する、というのが流行っているらしい。AWS Lambda ってのが有名。

ひとかたまりの処理を関数としてまとめ、それをクラウドサービスに切り出しておくことで、汎用的な処理を共通化し、アプリケーションと疎結合にできるワケだ。

この概念を聞いてから、「フロントエンドでも同じようなことはできないだろうか?」ということをぼんやり考えていた。例えば文字数チェックみたいな処理って、一度作ったら使い回しが利くものだ。それらを毎回 npm パッケージとしてインストールしてアプリにバンドルするのって、同じ関数の「コピー」があちこちのアプリに含まれているような状態で、なんだかなぁ、と思ったワケだ。上手いこと関数の CDN 化みたいなことはできないかな?と考えていた。

で、実用性はともかく、JavaScript の機能を利用すれば、それっぽいことは不可能ではないかな?と思ったのでメモする。

関数を提供するサーバ側

まずは、汎用的な関数を提供するサーバを用意する。http://example.com/check-min-length に GET 通信すると、以下のような JavaScript 関数の文字列を返してくれるとする。

function(str, minLength) {
  return str.length < minLength;
}

第1引数で指定した文字列が、第2引数で指定した最低文字数を超えているかどうかを Boolean で返すような関数だ。

関数を受け取って利用するクライアントアプリ側

次に、その関数を使うアプリ側の初期処理として、関数の文字列を取得して、それを関数として定義しようと思う。

// 関数をキャッシュするオブジェクト
const myFunctions = {};

// HTTP 通信で関数の文字列を取得する擬似コード。ココらへんは適宜 AJAX 通信するライブラリに読み替えて…
HttpClient.get('http://example.com/check-min-length')
  .then((funcStr) => {
    // 変数 funcStr は HTTP レスポンス。関数の文字列が取得できているとする
    
    // 関数の文字列を関数に変換し、オブジェクトにセットする
    myFunctions.checkMinLength = Function.call(null, 'return ' + funcStr)();
  });

ココで重要なのは、Function.call(null, 'return ' + checkMinLengthFuncStr)() という部分。コレにより、文字列を関数として実行し、関数を取り出している。

どんな動きをしているか追うために、「こう書いたらこう出力される」というのを順に書いてみる。

  • const myFunc = Function.call(null, funcStr);console.log(myFunc);
function anonymous() {
  function(str, minLength) {
    return str.length < minLength;
  }
}

変数 myFunc に、Function.call(null, funcStr) を代入する。コレだと、目的の関数を抱えた無名関数、という構造になってしまう。

  • Function.call(null, 'return ' + funcStr)
function anonymous() {
  return function(str, minLength) {
    return str.length < minLength;
  }
}

そこで、Function.call() の第2引数に retrun を付与して、関数を return する無名関数に仕立て上げる。

  • Function.call(null, 'return ' + funcStr)();
function(str, minLength) {
  return str.length < minLength;
}

「関数を返す無名関数」にできたら、Function.call()() の形にして、「関数を返す無名関数」を実行して、「関数」を受け取る。コレが完成形。

このようにして関数の文字列を関数として myFunctions.checkMinLength に定義できたら、あとはコレをいつでも使えるだろう。

// myStr が5文字未満の場合は…
if(myFunctions.checkMinLength(myStr, 5)) {
  // 何か処理する
}

こんな感じ。

実質的に eval だよね…

さて、ココまでやってみたものの、コレって、どこかの URL から JavaScript コードを拾ってきてそれを実行するワケで、メチャクチャ危険なセキュリティホールになりそう。

関数を提供する API の URL や通信先を偽装したりできれば、任意の関数を実行させられることになるし、ココらへんのセキュリティをどう担保したらいいやら。

FaaS の考え方と違うのは、API サーバは関数のテキストをレスポンスさえできれば良く、計算リソースはクライアントに委ねられるのがメリットかなと思うんだけど、実質 eval なので危険すぎるか…。

実践AWS Lambda ~「サーバレス」を実現する新しいアプリケーションのプラットフォーム~

実践AWS Lambda ~「サーバレス」を実現する新しいアプリケーションのプラットフォーム~