Humanity

Edit the world by your favorite way

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)ディレクトリトラバーサル対策です。便利。

合わせて読みたい

www.kaoriya.net

open-browser.vim で Windows Subsystem for Linux 上の Vim から URL 開けるようになった

github.com

これで WSL 上であろうとなかろうと意識せず URL を開けるはずです。 最近は WSL の Vim ばっかり使ってるので個人的にうれしい機能追加です(追加したの自分だけど)。


WSL は Windows の世界と分断されてると思い込んでたのですが、 /mntWindowsファイルシステムを触れるのは知ってたものの、 Windows の実行ファイル(PE フォーマットの実行ファイル)も起動できるのは知りませんでした。 bash から /mnt/c/Windows/System32/notepad.exe とか開けるんですね。

というわけで直接 Windows で動いてる Vim *1 でも叩いてる rundll32.exe を直接叩いて URL を渡すだけでした。あっけない。

*1:この表現は誤解を招くような… WSL も同じサブシステム上で動いてるし。なんて言えばいいんだ?

Go のバージョンマネージャー gvm で gvm install したら「ERROR: Failed to compile」って言われた時

github.com

issue には上げたけど日本語でも書く。 結果から言うと自分が README.md よく読んでなかっただけ。 でも gvm ももうちょっと気を利かせてくれてもいいのになーと思ったので上の issue でお願いしたという経緯です。

解決法

Go 1.5 以上をコンパイルする場合は GOROOT_BOOTSTRAP って環境変数を定義する必要があります。 これは .bash_profile かなんかで

export GOROOT_BOOTSTRAP=$GOROOT

して gvm install すればいいのですが、「GOROOT?そんなん定義したことないんだけど…」って人もいるかと思います (現に今は GOROOT 環境変数定義する必要はないらしいです)。

GOROOTJava にとっての JAVA_HOME みたいなもので、go コマンド等のベースパスを指定するものです。

で、じゃあどうすればいいのかというと、go コマンド自体に聞いてしまえばいいとのこと。

export GOROOT_BOOTSTRAP=$(go env GOROOT)

これで GOROOT_BOOTSTRAP には go コマンド等のベースパスが入ります。 もちろん go コマンドに PATH が通ってないとダメなので後述するように先に PATH を設定してやります。

どう設定すればいいか

自分の Go 関連の .bash_profile を載せておきます(今回の話に関係ないのもありますが)。

# Go 1.5 以下で vendoring が有効になる
# http://yoru9zine.hatenablog.com/entry/2016/02/02/054922
export GO15VENDOREXPERIMENT=1

if true; then
  # gvm の go コマンドを使う
  [[ -s "/home/tyru/.gvm/scripts/gvm" ]] && source "/home/tyru/.gvm/scripts/gvm"
  export GOROOT_BOOTSTRAP=$(go env GOROOT)
else
  # system 標準の go コマンドを使う
  export GOPATH="$HOME/go"
  export PATH="$GOPATH/bin:$PATH"
fi

true, false で gvm の Go か system の Go かを切り替えられるようにしてあります。