Go の net/http で Vue.js / Angular 1 などの HTML5 history mode に対応する
type html5Handler struct { fs http.FileSystem routes []staticRoute } // Look up path when Vue.js HTML5 history mode is enabled // https://router.vuejs.org/ja/essentials/history-mode.html func enableHTML5Mode(fs http.FileSystem, routes []staticRoute) http.FileSystem { return &html5Handler{fs, routes} } func (handler *html5Handler) Open(p string) (http.File, error) { file, err := handler.fs.Open(p) if err != nil { return searchHTML5Mode(handler, p, err) } return file, err } // * Try again after removing prefix if it has // * Returns index.html otherwise func searchHTML5Mode(handler *html5Handler, p string, err error) (http.File, error) { p = path.Clean(p) for _, route := range handler.routes { if strings.HasPrefix(p, route.basePath) { file, err := handler.fs.Open(strings.TrimPrefix(p, route.basePath)) if err == nil { return file, nil } } if route.indexPattern.MatchString(p) { return handler.fs.Open("/index.html") } } return nil, err }
で
http.Handle("/", http.FileServer(http.Dir("assets/site")))
とかやってたところを
routes := []staticRoute{ staticRoute{indexPattern: regexp.MustCompile(`^/feeds/[0-9]+$`), basePath: `/feeds`}, } http.Handle("/", http.FileServer(enableHTML5Mode(http.Dir("assets/site"), routes)))
に変える。
そうすると routes
に定義したパス(この場合は /feeds/:id
)に GET でアクセスした場合にも index.html が返り、
index.html の <script>
タグや <img>
タグ等で指定した静的ファイルも(basePath
が取り除かれて再検索されるので)返るようになる。
もちろんもともと http.Dir()
で指定してたパスの階層とマッチする場合は今まで通り問題なく静的ファイルが返される。
今回やったのは func (FileSystem) Open(p string) (http.File, error)
で第2返り値の error が non-nil だった場合に指定された routes
で再検索してるだけです。
ちなみに path.Clean(p)
はディレクトリトラバーサル対策です。便利。