概要
LoopBackは3種類のフックを提供しています。
- リモートフック リモートメソッドの呼出し前または呼出し後に実行する。メソッドは独自のリモートメソッドでも、 PersistedModel から継承した標準的な作成・取得・更新・削除メソッドでもよい。 REST操作に対応するNodeメソッドについてはPersistedModelのREST APIを参照。
- 操作フック モデルが作成・取得・更新・削除の操作を行った時に実行する。操作フックは、非推奨になったモデルフックを置き換えるものである。
- コネクタフック はリクエストがデータソースコネクタを要求する前か、コネクタの応答後に実行する。
リモートフック は、クライアントによって呼び出されたリモートメソッドが実行される前、または後に、関数を実行できるようにするものです。
beforeRemote()
はリモートメソッドの前に実行します。afterRemote()
はリモートメソッドが正常に終了した後に実行します。afterRemoteError()
はリモートメソッドがエラーになった後に実行します。
Tip: beforeRomote フックを使うと、リモートメソッドへの入力を検証あるいは無害化できます。 beforeRemote フックは、リモートメソッドが実行される 前に 実行されるので、リモートメソッドの入力にアクセスすることはできますが、結果にはできません。
afterRemote フックは、リモートメソッドの結果を、クライアントに送信する前に、修正・記録・その他に使用することができます。 afterRemote フックは、リモートメソッドが実行された 後に 実行されるので、リモートメソッドの結果にアクセスできますが、入力引数を変更することはできません。
シグネチャ
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フックを定義しています。
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”で終わる名前のリモートメソッドが実行されるたびに呼び出されます
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();
});
Important:
フックの2番目の引数は(上の例では user
)は、ctx.result
ですが、常に利用できるわけではありません。
以下に、リモートメソッドが呼び出される前に関数を実行するためのワイルドカードを使用したリモートフックの例を示します。
// ** 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();
});
値を新しいオブジェクトにコピーすることによって、返されるフィールドを効果的にホワイトリストする安全な手段です。
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()
が失敗したときに追加のアクションを実行するには以下のようにします。
Dog.afterRemoteError('prototype.speak', function(ctx, next) {
console.log('Cannot speak!', ctx.error);
next();
});
エラーオブジェクトにメタデータを加えます。
Dog.afterRemoteError('**', function(ctx, next) {
if (!ctx.error.details) ctx.error.details = {};
ctx.error.details.info = 'intercepted by a hook';
next();
})
呼び出し元に別のエラーを報告します。
Dog.afterRemoteError('prototype.speak', function(ctx, next) {
console.error(ctx.error);
next(new Error('See server console log for details.'));
});
コンテキストオブジェクト
リモートフックには、プロトコル固有のデータ(HTTPの場合 req
とres
)を含むコンテキストオブジェクトctx
が用意されています。ctx
オブジェクトは、プロトコル間で一貫したAPI一式を持っています。
loopback.rest() ミドルウェアを使うアプリケーションは、次の追加ctx
プロパティが提供されます。
afterRemoteError()
フックに渡されたコンテキストオブジェクトには、リモートメソッドによって報告されたエラーが設定された追加のプロパティ ctx.error
があります。
その他のプロパティ:
ctx.args
- HTTPリクエストの引数定義を含むオブジェクト。arg定義を使用して、リクエストから値を探します。これらは、リモートメソッドへの入力値です。ctx.result
- 引数名をキーとするオブジェクト 例外:rootプロパティがtrueの場合、ルートがtrueに設定されている引数の値になります。
ctx.req.accessToken
リモートメソッドを呼び出したユーザの accessToken
。
Important:
ログインしているユーザー(または他のプリンシパル)によってリモートメソッドが呼び出されない場合、ctx.req.accessToken
は undefined です。
ctx.result
afterRemote
フック中で ctx.result
には、クライアントに送信される予定のデータが格納されます。このオブジェクトを変更して送信前にデータを変換します。
Important:
ctx.result
の値は、常に利用できるとは限りません。
リモートメソッドが返された値を明示的に指定している場合のみ、ctx.resultが設定されます。したがって、リモートメソッドは次のようなことをしなければなりません。
MyModel.remoteMethod('doSomething', {
// ...
returns: {arg: 'redirectUrl', type: 'string'}
});