PHPのフレームワークのSlimやLaravelを使っていると、ルーティングした際にコントローラーを実行する前段階でやりたい処理をMiddlewareという形で定義しておき、ルーター毎に設定する機構がある。
下記の例では、管理画面のダッシュボードは、管理者がログインしていないと表示できないので、事前にそのチェックをして、ログインしていなければログインページにリダイレクトする。
Route::group(["middleware" => "admin"], function () {
// ダッシュボード
Route::get('/dashboard', 'DashboardController@index');
});
class AdminAuthenticate
{
/**
*
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard('admin')->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('admin/login');
}
}
return $next($request);
}
}
SPAでフロントエンドのルーティングするフレームワークを利用している時も、同様の処理を行いたいケースがあるが、JS製のルーターにはMiddleware的な機能を持っているものはなく(というか見つけきれず)、例えばユーザーがログイン状態かどうかのチェックを毎回ルーターの中に記述する必要があった。(下記はBackbone.jsのルーターの例)
var Router = Backbone.Router.extend({
routes: {
// マイページ
'mypage': 'mypage',
},
mypage: function () {
var that = this;
// 登録処理が含まれるので、tokenを取得してからページ表示
App.User.checkLoginStatus() // ログイン状態を取得
.done(function () {
if (state == '' && App.User.login_status) { // ログイン状態で内場合の処理
that.navigate('', true);
}else{
App.utils.getToken() // ログイン状態なら、トークンを取得して画面をレンダリング etc..
.done(function () {
App.Mypage.render(state);
});
}
})
},
...
})
毎回ルーティングの処理に上記の様な設定を書くのは辛いと感じ、LaravelのようなMiddlewareを実装できないかと思って作ってみた。
webpack
+babel loarder
を使って実装。Riot.jsのRouter機能に対して下記の様な形で実装してみた。
export default class {
// store services.
constructor (arr) {
this.functions = arr
}
// MiddleWareを実行する
execute (arr) {
var exe = []
var that = this
if (typeof arr == 'object') {
arr.map(function (v, i) {
if (typeof that.functions[v] == 'function') {
const promise = that.promise()
exe.push(that.functions[v](promise))
}
// Deferredオブジェクトを返却
})
return $.when.apply(null, exe)
} else {
if (typeof that.functions[arr] == 'function') {
return that.functions[arr]()
}
}
}
promise () {
return new window.$.Deferred
}
}
export default {
// ページをフェードアウトしながら非表示にしたりする
pageHide: function (promise) {
var $body = $('body')
$body.addClass('hide')
$body.removeClass('show')
// trnsitionendの検知
var transitionend = whichTransitionEvent();
$body.on(transitionend, function (e) {
windowTop(); // 画面をトップに戻したり。。
$body.off(transitionend)
return promise.resolve() // 完了したらpromiseを返却する
})
if (!transitionend) {
$body.trigger(transitionend)
}
return promise.reject(): // 上記以外のパターンはreject
}
}
import Middleware from './Middleware' // ミドルウェアを読み込み
import service from './service'
const middleware = new Middleware(service)
export default middleware // インスタンスを返す
import middleware from './middleware/stored' // ミドルウェアのインポート
// トップページ
riot.route('', function () {
middleware.execute(['pageHide']) // pageHideを実行してページのロードを行う
.done(function () {
require.ensure([], function () {
require('./ui/index.tag')
riot.mount('#appRoot', 'index')
}, 'top')
})
}
使い方としては、service.js
に実行したいmiddlewareを定義して、ルーティングの時にmiddleware.execute
で実行するという流れ。
コールバックで書けるようにMiddleware.promise
で任意のpromise機構を設定する事ができるようにしている。(例ではjQueryのDeferredを設定)。
例えば他にログインチェックなんかを入れたい場合は下記の様に書ける
export default {
// ページをフェードアウトしながら非表示にしたりする
pageHide: function (promise) {
...
},
// ログインチェックを行う
loginCheck: function (promise) {
App.LoginCheck().done(function () {
return promise.resolve(): // 上記以外のパターンはreject
})
.fail(function () {
riot.route('login') //ログインページにリダイレクト
return promise.reject(): // reject
})
}
}
import middleware from './middleware/stored' // ミドルウェアのインポート
// トップページ
riot.route('', function () {
middleware.execute(['loginCheck', 'pageHide']) // loginCheckとpageHideを実行してページのロードを行う
.done(function () {
require.ensure([], function () {
require('./ui/index.tag')
riot.mount('#appRoot', 'index')
}, 'top')
})
}
フロントはフロントで、ページ表示の前処理としてローディング画面表示したり、アニメーション効果を入れたかったりしたいときとかあるので、これを実装してかなりスッキリと書ける様になった。
今はまだjQueryに依存しているので、promiseの設定を自由に選べたり、(gulpのタスクみたいに)依存関係をmiddlewareないで設定出来るようにできたら良いな〜と思ってます。