リモートフックは、モデルのリモートメソッドが実行される前か、実行された後に動きます。
Page Contents

概要

LoopBackは3種類のフックを提供しています。

  • リモートフック リモートメソッドの呼出し前または呼出し後に実行する。メソッドは独自のリモートメソッドでも、 PersistedModel から継承した標準的な作成・取得・更新・削除メソッドでもよい。 REST操作に対応するNodeメソッドについてはPersistedModelのREST APIを参照。
  • 操作フック モデルが作成・取得・更新・削除の操作を行った時に実行する。操作フックは、非推奨になったモデルフックを置き換えるものである。
  • コネクタフック はリクエストがデータソースコネクタを要求する前か、コネクタの応答後に実行する。

リモートフック は、クライアントによって呼び出されたリモートメソッドが実行される前、または後に、関数を実行できるようにするものです。

  • beforeRemote() はリモートメソッドの前に実行します。
  • afterRemote() はリモートメソッドが正常に終了した後に実行します。
  • afterRemoteError() はリモートメソッドがエラーになった後に実行します。

シグネチャ

beforeRemote()afterRemote() も同じシグネチャです。以下の構文は beforeRemote を使っていますが、afterRemoteも同じです。

静的な独自のリモートメソッドの場合は、以下のようになります。

modelName.beforeRemote( methodName, function( ctx, next) {
    //...
    next();
});

ここで、

  • modelName は、リモートメソッドをもつモデル名です。
  • methodName は、リモートメソッドの名前です。

upsert()や、create() のような インスタンスメソッドあるいは静的な組み込みメソッドは、3番目の引数としてコールバックが必要です。

modelName.beforeRemote( methodName, function( ctx, modelInstance, next) {
    //...
    next();
});

afterRemoteError() は少し異なるシグネチャです。ハンドラ関数は2つの引数しかありません。

modelName.afterRemoteError( methodName, function( ctx, next) {
    //...
    next();
});

ここで、

  • modelName は、リモートフックが接続されたモデルの名前です。
  • methodName は、リモートフックを起動するメソッドの名前です。独自のリモートメソッドでも、 PersistedModel から継承した、標準の作成・読取・更新・削除メソッドでも構いません。 一つ以上のメソッドにマッチさせるために、ワイルドカードを使うことができます。(以下参照)
  • ctxコンテキストオブジェクトです。
  • modelInstance は作用を受けるモデルのインスタンスです。

next()の呼出しを含む上の構文は、リモートフックのコールバック関数のどこかで、必ず next() を呼び出さねばならないことのリマインダです。 必ずしも関数の最後である必要はありませんが、関数の処理が終わる前に、どこかで呼び出す必要があります。

ワイルドカード

methodName では、以下のようなワイルドカードを使えます。

  • アスタリスク '*' は、最初に区切り文字 '.' (ピリオド)が現れるまで、あらゆる文字にマッチします。
  • 2重アスタリスクは、区切り文字 '.' (ピリオド)を含むあらゆる文字にマッチします。

例えば、'*.*' とすると、全ての静的メソッドにマッチし、'prototype.*'とすると、全てのインスタンスメソッドにマッチします。

次の例では、revEngine() リモートメソッドのbeforeRemoteフックとafterRemoteフックを定義しています。

common/models/car.js

module.exports = function(Car) {
  // リモートメソッド
  Car.revEngine = function(sound, cb) {
    cb(null, sound - ' ' - sound - ' ' - sound);
  };
  Car.remoteMethod(
    'revEngine',
    {
      accepts: [{arg: 'sound', type: 'string'}],
      returns: {arg: 'engineSound', type: 'string'},
      http: {path:'/rev-engine', verb: 'post'}
    }
  );
  // リモートメソッド前のフック
  Car.beforeRemote('revEngine', function(context, unused, next) {
    console.log('Putting in the car key, starting the engine.');
    next();
  });
  // リモートメソッド後のフック
  Car.afterRemote('revEngine', function(context, remoteMethodOutput, next) {
    console.log('Turning off the engine, removing the key.');
    next();
  });
...
}

次の例では、リモートメソッド名にワイルドカードを使用しています。このリモートフックは、 “save”で終わる名前のリモートメソッドが実行されるたびに呼び出されます

common/models/customer.js

Customer.beforeRemote('*.save', function(ctx, unused, next) {
  if(ctx.req.accessToken) {
    next();
  } else {
    next(new Error('must be logged in to update'))
  }
});

Customer.afterRemote('*.save', function(ctx, user, next) {
  console.log('user has been saved', user);
  next();
});

以下に、リモートメソッドが呼び出される前に関数を実行するためのワイルドカードを使用したリモートフックの例を示します。

common/models/customer.js

// ** prototype.* と *.* の両方にマッチする
Customer.beforeRemote('**', function(ctx, user, next) {
  console.log(ctx.methodString, 'was invoked remotely'); // customers.prototype.save was invoked remotely
  next();
});

Other wildcard examples
// 全ての静的メソッドの前に実行される 例:User.find
Customer.beforeRemote('*', ...);

// 全てのインスタンスメソッドの前に実行される 例:User.prototype.save
Customer.beforeRemote('prototype.*', ...);

// パスワードハッシュがクライアントに送信されるのを防ぐ
Customer.afterRemote('**', function (ctx, user, next) {
  if(ctx.result) {
    if(Array.isArray(ctx.result)) {
      ctx.result.forEach(function (result) {
        delete result.password;
      });
    } else {
      delete ctx.result.password;
    }
  }

  next();
});

値を新しいオブジェクトにコピーすることによって、返されるフィールドを効果的にホワイトリストする安全な手段です。

common/models/account.js

var WHITE_LIST_FIELDS = ['account_id', 'account_name'];

Account.afterRemote('**', function(ctx, modelInstance, next) {
  if (ctx.result) {
    if (Array.isArray(modelInstance)) {
      var answer = [];
      ctx.result.forEach(function (result) {
        var replacement ={};
        WHITE_LIST_FIELDS.forEach(function(field) {
          replacement[field] = result[field];
        });
        answer.push(replacement);
      });
    } else {
      var answer ={};
      WHITE_LIST_FIELDS.forEach(function(field) {
        answer[field] = ctx.result[field];
      });
    }
    ctx.result = answer;
  }
  next();
});

afterRemoteErrorの例

インスタンスメソッドspeak()が失敗したときに追加のアクションを実行するには以下のようにします。

common/models/dog.js

Dog.afterRemoteError('prototype.speak', function(ctx, next) {
  console.log('Cannot speak!', ctx.error);
  next();
});

エラーオブジェクトにメタデータを加えます。

common/models/dog.js

Dog.afterRemoteError('**', function(ctx, next) {
  if (!ctx.error.details) ctx.error.details = {};
  ctx.error.details.info = 'intercepted by a hook';
  next();
})

呼び出し元に別のエラーを報告します。

common/models/dog.js

Dog.afterRemoteError('prototype.speak', function(ctx, next) {
  console.error(ctx.error);
  next(new Error('See server console log for details.'));
});

コンテキストオブジェクト

リモートフックには、プロトコル固有のデータ(HTTPの場合 reqres)を含むコンテキストオブジェクトctxが用意されています。ctx オブジェクトは、プロトコル間で一貫したAPI一式を持っています。

loopback.rest() ミドルウェアを使うアプリケーションは、次の追加ctxプロパティが提供されます。

  • ctx.req: ExpressのRequestオブジェクト

  • ctx.res: ExpressのResponseオブジェクト

afterRemoteError() フックに渡されたコンテキストオブジェクトには、リモートメソッドによって報告されたエラーが設定された追加のプロパティ ctx.error があります。

その他のプロパティ:

  • ctx.args - HTTPリクエストの引数定義を含むオブジェクト。arg定義を使用して、リクエストから値を探します。これらは、リモートメソッドへの入力値です。
  • ctx.result - 引数名をキーとするオブジェクト 例外:rootプロパティがtrueの場合、ルートがtrueに設定されている引数の値になります。

ctx.req.accessToken

リモートメソッドを呼び出したユーザの accessToken

ctx.result

afterRemote フック中で ctx.result には、クライアントに送信される予定のデータが格納されます。このオブジェクトを変更して送信前にデータを変換します。

リモートメソッドが返された値を明示的に指定している場合のみ、ctx.resultが設定されます。したがって、リモートメソッドは次のようなことをしなければなりません。

MyModel.remoteMethod('doSomething', {
  // ...
  returns: {arg: 'redirectUrl', type: 'string'}
});