hapi のグローバルミドルウェア
◆ route 問わず共通の処理をして必要ならレスポンスをそこで返したい
◆ apache の mod_rewrite みたいなことしたい
◆ h.continue を使っても route は最初にマッチしたひとつしか実行できない
◆ if 文で別の route に処理を譲れない
◆ pre 機能は事前処理できるけど 個別 route にしか設定できない
◆ デフォルトの route の設定には付けれない
◆ onPreHandler を使う
◆ handler より前にレスポンスを返す場合は takeover メソッド必須
◆ ファイルの有無は自分でチェック必要 (inert に任せれない)
◆ apache の mod_rewrite みたいなことしたい
◆ h.continue を使っても route は最初にマッチしたひとつしか実行できない
◆ if 文で別の route に処理を譲れない
◆ pre 機能は事前処理できるけど 個別 route にしか設定できない
◆ デフォルトの route の設定には付けれない
◆ onPreHandler を使う
◆ handler より前にレスポンスを返す場合は takeover メソッド必須
◆ ファイルの有無は自分でチェック必要 (inert に任せれない)
やりたいことは apache の rewrite みたいなものです
public フォルダにアクセスされたパスのファイルがあるとそれを返して なければ指定された route ごとの処理をします
express や koa は middleware を使えばそれだけでできそうなのですが hapi にはグローバルなミドルウェアがないみたいです
全体で登録されたものを順番に実行していくのではなくて 定義した route ごとに設定を書いていくみたいです
先に全体にマッチする route を定義して ファイルがあれば返してなければ h.continue を返せばおっけい と思ってやってみると
まず hapi では route は配列で定義できますが 配列の順番に意味はありません
決められたルールで順番が決まりその順番でマッチするか確認して最初にマッチしたものになります
条件が緩く全体にマッチするものは優先度が低いので先にマッチさせることが出来ません
また 試してみると先にマッチできたとしても h.continue で次にマッチする route を実行することは出来ませんでした
route の handler が h.continue を返しても null に置き換える処理になっていました
route の定義など設定ファイル風に書けるところが express 系と違って良い部分なのですが 自由度も低めな印象です
handler を実行する前に実行できるようです
例を見るとそれぞれの route 定義に書いていて 全部の route に書くのはさすがに嫌なので共通なのがないか探すと Hapi.server のオプションに routes で全部の route 共通のデフォルト設定が書けるようです
ここで設定しておけばどの route に対しても pre が実行できる と思ったのですがエラーが起きました
調べてみると同じエラーで issue があって global に pre は設定できないという回答でした
onRequest や onPreAuth など用意された名前に関数を設定できます
Chrome 拡張機能の webRequest の図みたいなものです
hapi の流れはここにあるものです
onRequest だと最初過ぎで その後に認証などがあるので 静的ファイルなら認証などはいらないというならここでいいですが それ以外はここでやらないほうがよさそうです
もともと pre でやるつもりだったので onPreHandler にしました
これで動きましたが 困ったところが 2 つありました
handler 以前のタイミングでレスポンスを確定させて返す場合は takeover メソッドを呼び出す必要があります
ないとエラーになります
もう一つですが ファイルの有無を自分で判断しないといけません
h.file の返り値で判断できたら楽なのですけど 実行時点ではパスの解決だけでファイルの有無をチェックしたり read したりは起きないみたいです
なのでこのタイミングだとファイルがあるかは h.file の返り値であるレスポンスからはわかりません
自分で確認する必要があります
なんか二度手間感がありますね
どうせなら直接自分で readFile して返そうかとも思いましたが inert は単純にファイルを読んで返すだけじゃなくて色々やってくれてるみたいでそれらまで自分でやりたくないのでファイルの有無だけチェックして処理は inert 任せです
コード的にはそこまで複雑にならずにできましたが これがベストな方法かもわかりませんし この辺りは express 系に比べると扱いづらいと感じます
route 側で静的ファイルを返すものです
こうすると onPreResponse のところでファイルが見つからないとエラーを取得できます
しかし route を定義するなら onPreResponse のところで自力で分岐させてとなるので framework の書き方を捨ててるようなものなので 良いとは言えません
ただ api 等がない静的ファイル or デフォルトファイルだけなら困りはしないと思います
404 の代わりに常に index.html を返すだけですから
それでも将来的に特別な route が増える可能性がないとは言えないのでやっぱりオススメはできません
それに問題があって onPreResponse で h.file のレスポンスを返しても正しくファイルがクライアントに送信されません
上で書いたように file レスポンスが解決されるタイミングが決まってるのか onPreResponse からレスポンス送信までに file レスポンスが解決されないようです
この方法にするなら自分で readFile して返すことになります
public フォルダにアクセスされたパスのファイルがあるとそれを返して なければ指定された route ごとの処理をします
express や koa は middleware を使えばそれだけでできそうなのですが hapi にはグローバルなミドルウェアがないみたいです
全体で登録されたものを順番に実行していくのではなくて 定義した route ごとに設定を書いていくみたいです
h.continue
h.continue を返せばレスポンスを変更せず続けられるというのを見つけました先に全体にマッチする route を定義して ファイルがあれば返してなければ h.continue を返せばおっけい と思ってやってみると
まず hapi では route は配列で定義できますが 配列の順番に意味はありません
決められたルールで順番が決まりその順番でマッチするか確認して最初にマッチしたものになります
条件が緩く全体にマッチするものは優先度が低いので先にマッチさせることが出来ません
また 試してみると先にマッチできたとしても h.continue で次にマッチする route を実行することは出来ませんでした
route の handler が h.continue を返しても null に置き換える処理になっていました
route の定義など設定ファイル風に書けるところが express 系と違って良い部分なのですが 自由度も低めな印象です
pre
調べてみると pre というオプションが使えるようですhandler を実行する前に実行できるようです
例を見るとそれぞれの route 定義に書いていて 全部の route に書くのはさすがに嫌なので共通なのがないか探すと Hapi.server のオプションに routes で全部の route 共通のデフォルト設定が書けるようです
ここで設定しておけばどの route に対しても pre が実行できる と思ったのですがエラーが起きました
調べてみると同じエラーで issue があって global に pre は設定できないという回答でした
onPreHandler
なにか方法がないか探していると hapi が処理する順番の途中で 処理を組み込めるようですonRequest や onPreAuth など用意された名前に関数を設定できます
Chrome 拡張機能の webRequest の図みたいなものです
hapi の流れはここにあるものです
onRequest だと最初過ぎで その後に認証などがあるので 静的ファイルなら認証などはいらないというならここでいいですが それ以外はここでやらないほうがよさそうです
もともと pre でやるつもりだったので onPreHandler にしました
const Hapi = require("hapi")
const inert = require("inert")
const path = require("path")
const fs = require("fs")
const server = new Hapi.Server({ port: 80 })
!(async function(){
await server.register(inert)
server.ext("onPreHandler", function(req, h){
const real_path = path.join(__dirname, req.path)
if (fs.existsSync(real_path) && fs.statSync(real_path).isFile()){
return h.file(real_path).takeover()
}
return h.continue
})
server.route([
{
method: "GET",
path: "/{path*}",
async handler(req, h) {
return h.file("index.html")
},
},
{
method: "GET",
path: "/abc/{path*}",
async handler(req, h) {
return "hello"
},
},
])
await server.start()
})()
これで動きましたが 困ったところが 2 つありました
handler 以前のタイミングでレスポンスを確定させて返す場合は takeover メソッドを呼び出す必要があります
ないとエラーになります
もう一つですが ファイルの有無を自分で判断しないといけません
h.file の返り値で判断できたら楽なのですけど 実行時点ではパスの解決だけでファイルの有無をチェックしたり read したりは起きないみたいです
なのでこのタイミングだとファイルがあるかは h.file の返り値であるレスポンスからはわかりません
自分で確認する必要があります
なんか二度手間感がありますね
どうせなら直接自分で readFile して返そうかとも思いましたが inert は単純にファイルを読んで返すだけじゃなくて色々やってくれてるみたいでそれらまで自分でやりたくないのでファイルの有無だけチェックして処理は inert 任せです
コード的にはそこまで複雑にならずにできましたが これがベストな方法かもわかりませんし この辺りは express 系に比べると扱いづらいと感じます
onPreResponse
もうひとつ考えていたのが逆パターンですroute 側で静的ファイルを返すものです
server.ext("onPreResponse", function(req, h){
if(req.res instanceof Error){
return h.file("index.html") // not working
}else{
return h.continue
}
})
server.route([
{
method: "GET",
path: "/{path*}",
async handler(req, h) {
return h.file(req.path.substr(1))
},
},
])
こうすると onPreResponse のところでファイルが見つからないとエラーを取得できます
しかし route を定義するなら onPreResponse のところで自力で分岐させてとなるので framework の書き方を捨ててるようなものなので 良いとは言えません
ただ api 等がない静的ファイル or デフォルトファイルだけなら困りはしないと思います
404 の代わりに常に index.html を返すだけですから
それでも将来的に特別な route が増える可能性がないとは言えないのでやっぱりオススメはできません
それに問題があって onPreResponse で h.file のレスポンスを返しても正しくファイルがクライアントに送信されません
上で書いたように file レスポンスが解決されるタイミングが決まってるのか onPreResponse からレスポンス送信までに file レスポンスが解決されないようです
この方法にするなら自分で readFile して返すことになります