見出し画像

【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だったりして、インターフェースの拡張性の高さが垣間見えた。

この記事が気に入ったらサポートをしてみませんか?