【Go初学】net/http ServeMux と gorilla/mux Routerの挙動の違い
Webアプリケーションでcss/jsを読み込む際、標準パッケージnet/httpのHandle()と、gorilla/muxのHandle()の挙動の違いで詰まったため対応方法などを記載したい。
▼ファイル構成
ファイル構成などは以下の通りとなり、静的ファイルは public/static 下に配置するものとする。
// ファイル構成
├── public
│ ├── home
│ │ └── index.html
│ └── static
│ └── css
│ └── bootstrap.min.css
├── go.mod
├── go.sum
└── main.go
// index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="card">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<a href="#" class="btn btn-primary">button</a>
</div>
</div>
</div>
</body>
</html>
// main.go
package main
import (
"net/http"
"text/template"
)
func main() {
r := http.NewServeMux()
r.HandleFunc("/", handleHome)
http.ListenAndServe("localhost:8080", r)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("./public/home/index.html")
t.Execute(w, map[string]interface{}{})
}
index.html では「"/static/css/bootstrap.min.css"」を読み込もうとしており、ルート定義を追加して参照できるようにしたい。
▼共通ハンドラ
まずどちらも共通しているのはハンドラの生成。
URLパターンにマッチした際の処理として、静的ファイルを配置しているルートディレクトリから指定のファイルを探索してほしい。これは http パッケージの http.Dir() でルートディレクトリのパスを指定し、http.FileServer() でハンドラを生成できる。
fs := http.FileServer(http.Dir("./public"))
▼挙動の違い
■http.NewServeMux() を使う場合
func main() {
fs := http.FileServer(http.Dir("./public"))
r := http.NewServeMux()
r.Handle("/static/", fs)
r.HandleFunc("/", handleHome)
http.ListenAndServe("localhost:8080", r)
}
ルート定義は"/"で開始し、"static"下にある"css"を探索してほしいためルート指定は"/static/"となる。"/static/css/"指定でも読み込むことができる。
■mux.NewRouter()を使う場合
func main() {
fs := http.FileServer(http.Dir("./public"))
r := mux.NewRouter()
r.Handle("/static/", fs)
r.HandleFunc("/", handleHome)
http.ListenAndServe("localhost:8080", r)
}
http.NewServeMux()と同じ指定の仕方だと css 読み込みは 404NotFound になる。muxパッケージの場合ルート指定の探索が厳密なため、URL指定が"/static/"でないとマッチしない(つまり「http://localhost:8080/static/」であればマッチする)。
muxの場合は少なくとも2通りやり方があった。
func main() {
fs := http.FileServer(http.Dir("./public"))
r := mux.NewRouter()
r.Handle("/static/css/{key}", fs)
r.HandleFunc("/", handleHome)
http.ListenAndServe("localhost:8080", r)
}
1つはルート変数を使うやり方で、ルート指定を"/static/css/{key}"とすることで参照できるようになる。末尾の"{key}"は変数となり、cssディレクトリ下の全てのファイルに対応する。
もし参照したいファイルが「/public/static/hoge.css」だった場合は"static/{key}"で参照できる。
もう1つは PathPrefix() を使うと、ディレクトリ探索の際、このディレクトリから下を探索してほしいと指定できる。
func main() {
fs := http.FileServer(http.Dir("./public"))
r := mux.NewRouter()
r.PathPrefix("/static/").Handler(fs)
r.HandleFunc("/", handleHome)
http.ListenAndServe("localhost:8080", r)
}
PathPrefix() で "/static/" を指定することで、static以下のサブディレクトリも探索対象になる。
なので該当の"/static/css/〜"に加え、"/static/hoge/fuga/〜"なども参照が解決される。細かくディレクトリ管理したい場合はルート変数で直前のディレクトリまでを指定、まとめて扱いたい場合はPathPrefixで大雑把に指定という使い分けなんだろうか。
▼備考
テストする際にCSSがキャッシュされると正しく挙動の確認が行えないため、ChromeであればNetworkタブのDisable cacheをチェックしてテストを行う。
あとがき
全く同じ指定で挙動が異なったのは期待していなかったが、使い分けができることを学んだ。またhttp.FileServer()はFileSystemという大層なインターフェースを引数としているがここでの実体はただのstringだったりして、インターフェースの拡張性の高さが垣間見えた。