@fastify/static でファイルが見つからなかったら次を探す
◆ 全体に対する静的ファイルのサーブ設定とフォルダ固有の静的ファイルのサーブ設定があるときにフォルダ側でファイルが見つからなかったら全体の設定を見てほしい
◆ マルチプレフィックスでプラグイン登録せずマルチルートとして登録する
◆ マルチプレフィックスでプラグイン登録せずマルチルートとして登録する
前提
この記事の最後に書いた内容関連です読まなくても必要な事前情報はここで書いてます
Fastify はミドルウェアではなくルートベースなので アクセスされたパスやメソッドなどからルートが 1 つ決まってそのルートの処理が行われます
一致したルートの処理の中で条件に一致しなかったら次に一致するルートの処理ということはできないです
例えば 静的ファイルのサーブで ルートに一致したけどファイルがみつからなかったときに 次の処理ができないです
見つからなかったときの 404 用ハンドラを使うことはできなく無いですが ここで次に一致するルートを見つけてハンドラを呼び出すのは良い方法とは言えないです
対処方法の一つに @fastify/static を使わず ルートのハンドラではなくフックで処理するという方法がありますが 自力で実装する部分が増えるので今回は扱わないです
他には @fastify/static の設定で wildcard ルートを無効にする方法があります
wildcard を無効にすると指定したフォルダ内の全部のフォルダとファイルがルートとして登録されます
ファイルが無いならルートには一致しないので ルートにマッチしたのにファイルがなかったということはありえません(ファイルを消さない限り)
この方法は設定が簡単で良さそうですがデメリットもあります
ファイル数が多いと当然遅くなりますし 起動時点のファイルしか扱えません
ルートに登録されないので後からファイルを追加しても無視されます
一応速度を調べてみました
内部のフォルダとファイル数が約 2500 あるフォルダをルートとして設定しました
wildcard だとセットアップが 0.2s で終わる環境で wildcard を無効にすると 1.5s くらいになりました
起動時のみですし 実際 1000 もファイルがあることはあまり無いと思いますし 速度面はそれほど問題にならないかもしれません
リクエストが来たときのルーティング処理は fastify.printRoutes() をすればわかるようにパスの差分が発生する部分で区切られ高速でマッチングできるよう最適化されています
リクエストした瞬間にレスポンスが返ってきますし リクエスト時のパフォーマンスはほぼ無視で大丈夫そうです
これで良ければ wildcard の無効でいいですが サーバーを止めずに静的ファイルを更新したいことってけっこうあるのでできれば wildcard にしておきたいです
やりたいことは
そもそも何がしたいのか です優先度的に wildcard ルートに一致した時点で次に一致するルートは厳密なパス指定のルートではありません
次に一致するルートというのはもっと広い範囲の wildcard ルートです
そうなるとだいたいそのルートは同じく静的ファイルのサーブか固定のレスポンスかのどっちかになると思います
固定のレスポンスなら 404 用のハンドラとして登録で良いでしょう
つまりは次の静的ファイルのサーブが目的です
例を挙げるとこういうケースです
const static = import("@fastify/static")
await fastify.register(static, {
root: path.join(__dirname, "public"),
})
await fastify.register(static, {
root: path.join(__dirname, "app1/dist"),
prefix: "/app1",
decorateReply: false,
})
await fastify.register(static, {
root: path.join(__dirname, "app2/dist"),
prefix: "/app2",
decorateReply: false,
})
/app1 以下へのリクエストは app1/dist フォルダからサーブ
/app2 以下へのリクエストは app2/dist フォルダからサーブ
/ 以下へのリクエストは public フォルダからサーブ
次のファイルを用意します
app1/dist/index.js (1)
app2/dist/index.js (2)
そして以下のパスにアクセスします
/app1/index.js
/app2/index.js
/app1/index.js へのアクセスでは (1)
/app2/index.js へのアクセスでは (2)
が得られます
では次にこういうファイルも用意します
public/app1/index.js (3)
public/app1/foo.js (4)
そして /app1/index.js にアクセスすると何が返って来ると良いのでしょうか
全体を対象にしてる public フォルダよりも より厳密に指定している app1/dist のほうが優先してほしいです
実際に試してみるとこれは期待通り (1) が得られます
では /app1/foo.js にアクセスした場合は というとファイルが見つからないエラーです
ルート的に app1/dist から探すルートにマッチしてしまうので public から探すルートにはマッチしません
なので public にあっても無視されてしまいます
場合によってはこの挙動のほうが望ましいのかもしれません
/app1 は app1/dist と別フォルダを指定しているわけですから 全体 (/) を public と指定していても /app1 は除外してほしいこともあります
そういう場合は標準の設定でいいのですが 今回は見つからないならより広い wildcard ルートにフォールバックしてほしいです
対処方法
@fastify/static にはマルチルート機能があります上記の設定では /app1 は app1/dist を見るためにマルチプレフィックスとしてプラグインを登録しました
これをマルチルートとして登録すればうまくいきました
マルチルートでは root として設定するパスが複数あるもので prefix は 1 つだけです
public を 「/」 でサーブするのでこれに app1 と app2 をあわせます
virtual フォルダを作って その中に app1 と app2 へのシンボリックリンクを作ります
virtual/app1 -> app1/dist
virtual/app2 -> app2/dist
こうして root に virtual と public をこの順で指定します
await fastify.register(static, {
root: [
path.join(__dirname, "virtual"),
path.join(__dirname, "public"),
]
})
こうすれば登録される wildcard ルートは 「/*」 ひとつだけです
/app1/foo.js にアクセスがあったとき virtual/app1/foo.js を探して 見つからなければ public/app1/foo.js を探してくれます
virtual/app1 はシンボリックリンクなので 実際には app1/dist/foo.js を探して 見つからなければ public/app1/foo.js を探します
virtual フォルダとシンボリックリンクを作らないといけないのに少し抵抗はありますが virtual フォルダを見てどこにマウントしてるかの一覧が見れる利点はあるのでまぁ良しとしています