Go & Gorilla/Mux:Web Appを構築するために必要なすべて
Takashi Yamamoto
Infrastructure Engineer · Leapcell

はじめに
gorilla/mux
は、gorilla Web
開発ツールキットのルーティング管理ライブラリです。gorilla Web
開発パッケージは、Go
言語で Web
サーバーを開発するのに役立つツールキットです。フォームデータの処理 (gorilla/schema
)、websocket
通信 (gorilla/websocket
)、ミドルウェア (gorilla/handlers
)、session
管理 (gorilla/sessions
)、安全な cookie
処理 (gorilla/securecookie
) など、さまざまな側面をカバーしています。
mux
には以下の利点があります。
- 標準の
http.Handler
インターフェースを実装しており、net/http
標準ライブラリと組み合わせて使用できます。非常に軽量です。 - リクエストのホスト名、パス、パスプレフィックス、プロトコル、
HTTP
ヘッダー、クエリ文字列、HTTP
メソッドに基づいてハンドラーを照合できます。カスタムマッチングロジックもサポートしています。 - ホスト名、パス、リクエストパラメータで変数を使用でき、それらに正規表現を指定できます。
- 指定されたハンドラーにパラメータを渡して、完全な
URL
を構築できます。 - ルートのグループ化をサポートしており、管理とメンテナンスに便利です。
クイックスタート
この記事のコードでは Go Modules
を使用しています。
gorilla/mux
ライブラリをインストールする:
go get -u github.com/gorilla/gorilla/mux
次に、音楽情報を管理する Web
サービスを作成します。各楽曲は MusicID
によって一意に識別されます。
- 音楽の構造を定義する:
type Music struct { MusicID string `json:"music_id"` Name string `json:"name"` Artists []string `json:"artists"` Album string `json:"album"` ReleasedAt string `json:"released_at"` } var ( mapMusics map[string]*Music slcMusics []*Music )
- ファイルからデータをロードするために
init()
関数を定義する:
func init() { mapMusics = make(map[string]*Music) slcMusics = make([]*Music, 0, 1) data, err := ioutil.ReadFile("../data/musics.json") if err != nil { log.Fatalf("failed to read musics.json:%v", err) } err = json.Unmarshal(data, &slcMusics) if err != nil { log.Fatalf("failed to unmarshal musics:%v", err) } for _, music := range slcMusics { mapMusics[music.MusicID] = music } }
- 2 つのハンドラー関数を定義する。1 つはリスト全体を返し、もう 1 つは特定の楽曲を返す:
func MusicsHandler(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.Encode(slcMusics) } func MusicHandler(w http.ResponseWriter, r *http.Request) { music, ok := mapMusics[mux.Vars(r)["music_id"]] if!ok { http.NotFound(w, r) return } enc := json.NewEncoder(w) enc.Encode(music) }
- ハンドラーを登録する:
func main() { r := mux.NewRouter() r.HandleFunc("/", MusicsHandler) r.HandleFunc("/musics/{music_id}", MusicHandler) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
mux
の使い方は net/http
と非常によく似ています。まず、mux.NewRouter()
を呼び出して、*mux.Router
型のルーティングオブジェクトを作成します。このルーティングオブジェクトがハンドラーを登録する方法は、標準ライブラリの *http.ServeMux
とまったく同じです。つまり、HandleFunc()
メソッドを呼び出して、func(http.ResponseWriter, *http.Request)
型のハンドラー関数を登録し、Handle()
メソッドを呼び出して、http.Handler
インターフェースを実装するハンドラーオブジェクトを登録します。上記の例では、2 つのハンドラー関数が登録されています。1 つは音楽情報リストを表示するためのもので、もう 1 つは特定の楽曲の情報を表示するためのものです。
パス /musics/{music_id}
は変数を使用していることに注意してください。変数名は {}
の内部で指定されます。これはパスの特定の部分と一致する可能性があります。ハンドラー関数では、リクエスト r
のルーティング変数は mux.Vars(r)
を通じて取得できます。これは map[string]string
を返し、変数名を使用してアクセスできます。たとえば、上記の MusicHandler
の変数 music_id
へのアクセスなどです。
*mux.Router
は http.Handler
インターフェースも実装しているため、http.Handle("/", r)
のハンドラーオブジェクトパラメータとして直接登録できます。ここでは、ルートパス /
が登録されています。これは、すべてのリクエストの処理を *mux.Router
に委ねるのと同じです。
最後に、http.ListenAndServe(":8080", nil)
は、Web
サーバーを起動して受信リクエストを待機するために引き続き使用されます。
実行後、ブラウザで localhost:8080
と入力すると音楽リストが表示されます。localhost:8080/musics/[特定の MusicID]
と入力すると、対応する音楽の詳細情報が表示されます。使用プロセスから、mux
ライブラリは非常に軽量であり、標準ライブラリ net/http
とうまく統合できることがわかります。
正規表現を使用して変数のパターンを制限することもできます。MusicID
に固定パターンがあるとします(たとえば、M001-001
、つまり、文字 M
で始まり、その後に 3 桁の数字が続き、次に -
と 3 桁の数字で連結されます。これは正規表現 M\d{3}-\d{3}
で表すことができます)。変数と正規表現を区切るには、変数名の後に :
を追加します。
r.HandleFunc("/musics/{music_id:M\\d{3}-\\d{3}}", MusicHandler)
柔軟なマッチング方法
mux
は、リクエストをマッチングする豊富な方法を提供します。対照的に、net/http
は特定のパスのみを指定できます。これは少し扱いにくいです。
- ルートのドメイン名またはサブドメイン名を指定:
r.Host("musicplatform.com") r.Host("{subdomain:[a-zA-Z0-9]+}.musicplatform.com")
上記のルートは、ドメイン名 musicplatform.com
またはそのサブドメインからのリクエストのみを受け入れます。ドメイン名を指定するときに正規表現を使用できます。2 行目のコードは、サブドメイン名の最初の部分がいくつかの文字または数字である必要があることを制限します。
- パスプレフィックスを指定する:
// パスプレフィックス `/musics/` を持つリクエストのみを処理する r.PathPrefix("/musics/")
- リクエストメソッドを指定する:
// GET/POST リクエストのみを処理する r.Methods("GET", "POST")
- 使用されるプロトコル (
HTTP
/HTTPS
):
// https リクエストのみを処理する r.Schemes("https")
- ヘッダー:
// ヘッダー X-Requested-With の値が XMLHTTPRequest のリクエストのみを処理する r.Headers("X-Requested-With", "XMLHTTPRequest")
- クエリパラメータ (
URL
の?
の後の部分):
// クエリパラメータに key=value が含まれるリクエストのみを処理する r.Queries("key", "value")
- 組み合わせ条件:
r.HandleFunc("/", HomeHandler) .Host("musicstore.com") .Methods("GET") .Schemes("http")
さらに、mux
ではカスタムマッチャーも使用できます。カスタムマッチャーは、func(r *http.Request, rm *RouteMatch) bool
型の関数であり、リクエスト r
の情報に従ってマッチが成功したかどうかを判断します。http.Request
構造体には、多数の情報が含まれています。HTTP
メソッド、HTTP
バージョン番号、URL
、ヘッダーなど。たとえば、HTTP/1.1
のリクエストのみを処理する必要がある場合は、次のように記述できます。
r.MatchrFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 1 && r.ProtoMinor == 1 })
mux
はルート登録の順にマッチングすることに注意してください。したがって、通常は特別なルートを先頭に配置し、一般的なルートを後ろに配置することをお勧めします。逆の場合、特別なルートはマッチングされません。
r.HandleFunc("/specific", specificHandler) r.PathPrefix("/").Handler(catchAllHandler)
サブルート
プログラムモジュールをより明確にして、保守を容易にするために、ルートをグループ化および管理することがあります。Web サイトがビジネスを拡大し、さまざまな種類の音楽 (ポップ、ロックなど) に関連する情報を追加すると仮定します。個別の管理のために複数のサブルートを定義できます。
r := mux.NewRouter() ps := r.PathPrefix("/pop_musics").Subrouter() ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) rs := r.PathPrefix("/rock_musics").Subrouter() rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler)
サブルートは通常、パスプレフィックスによって制限されます。r.PathPrefix()
は *mux.Route
オブジェクトを返します。その Subrouter()
メソッドを呼び出すと、サブルートオブジェクト *mux.Router
が作成され、次にこのオブジェクトの HandleFunc/Handle
メソッドを介してハンドラー関数が登録されます。
サブルートの方法を使用すると、各パートのルートをそれぞれのモジュールに分散してロードすることもできます。pop_music.go
ファイルで InitPopMusicsRouter()
メソッドを定義して、ポップ音楽に関連するルートの登録を担当します。
func InitPopMusicsRouter(r *mux.Router) { ps := r.PathPrefix("/pop_musics").Subrouter() ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) }
rock_music.go
ファイルで InitRockMusicsRouter()
メソッドを定義して、ロック音楽に関連するルートの登録を担当します。
func InitRockMusicsRouter(r *mux.Router) { rs := r.PathPrefix("/rock_musics").Subrouter() rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler) }
main.go
のメイン関数:
func main() { r := mux.NewRouter() InitPopMusicsRouter(r) InitRockMusicsRouter(r) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
サブルートマッチングでは、パスプレフィックスを含める必要があることに注意してください。つまり、/pop_musics/
は PopMusicsHandler
と一致する可能性があります。
ルート URL
の構築
ルートに名前を付けることができます。例:
r.HandleFunc("/musics/{music_id}", MusicHandler).Name("music")
上記のルートにはパラメータがあります。パラメータ値を渡して完全なパスを構築できます。
fmt.Println(r.Get("music").URL("music_id", "M001-001")) // /musics/M001-001 <nil>
何が返されるかは *url.URL
オブジェクトであり、そのパス部分は /musics/M001-001
です。これはホスト名とクエリパラメータにも当てはまります。
r := mux.Router() r.Host("{name}.musicplatform.com"). Path("/musics/{music_id}"). HandlerFunc(MusicHandler). Name("music") url, err := r.Get("music").URL("name", "user1", "music_id", "M001-001")
パスのすべてのパラメータを指定する必要があり、値は指定された正規表現を満たしている必要があります(ある場合)。実行出力は次のとおりです。
$ go run main.go http://user1.musicplatform.com/musics/M001-001
URLHost()
を呼び出してホスト名部分のみを生成し、URLPath()
を呼び出してパス部分のみを生成できます。
ミドルウェア
mux
はミドルウェアタイプ MiddlewareFunc
を定義します。
type MiddlewareFunc func(http.Handler) http.Handler
このタイプを満たすすべての関数は、mux
のミドルウェアとして使用できます。ミドルウェアは、ルーティングオブジェクト *mux.Router
の Use()
メソッドを呼び出すことによって適用されます。ミドルウェアを作成するときは、通常、元のハンドラーが渡されます。ミドルウェアでは、元のハンドラー関数が手動で呼び出され、その後、前後に一般的な処理ロジックが追加されます。
func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.RequestURI) next.ServeHTTP(w, r) }) }
Web サイトで音楽関連のページにアクセスするにはログインが必要だとします。このロジックを処理するミドルウェアを作成できます。Cookie
が存在しないか違法な場合は、ログインページにリダイレクトされます。ログインに成功すると、キー token
を持つ Cookie
が生成され、ログインが成功したことが示されます。
func login(w http.ResponseWriter, r *http.Request) { // ここでは、html/template を使用してテンプレートを解析し、ログインページを表示すると仮定します loginTemplate.ExecuteTemplate(w, "login.tpl", nil) } func doLogin(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.Form.Get("username") password := r.Form.Get("password") if username != "user" || password != "123456" { http.Redirect(w, r, "/login", http.StatusFound) return } token := fmt.Sprintf("username=%s&password=%s", username, password) data := base64.StdEncoding.EncodeToString([]byte(token)) http.SetCookie(w, &http.Cookie{ Name: "token", Value: data, Path: "/", HttpOnly: true, Expires: time.Now().Add(24 * time.Hour), }) http.Redirect(w, r, "/", http.StatusFound) }
ログインページを表示するために、いくつかの template
テンプレートファイルが作成され、html/template
を使用して解析されます。
- ログイン表示ページ:
<!-- login.tpl --> <form action="/login" method="post"> <label>Username:</label> <input name="username"><br> <label>Password:</label> <input name="password" type="password"><br> <button type="submit">Login</button> </form>
- メインページ:
<ul> <li><a href="/pop_musics/">Pop Music</a></li> <li><a href="/rock_musics/">Rock Music</a></li> </ul>
- 音楽リストと詳細ページ (例):
<!-- pop_musics.tpl --> <ol> {{ range . }} <li> <p>Song Name: <a href="/pop_musics/{{ .MusicID }}">{{ .Name }}</a></p> <p>Release Date: {{ .ReleasedAt }}</p> <p>Artists: {{ range .Artists }}{{ . }}{{ if not $.Last }}, {{ end }}{{ end }}</p> <p>Album: {{ .Album }}</p> </li> {{ end }} </ol>
<!-- pop_music.tpl --> <p>MusicID: {{ .MusicID }}</p> <p>Song Name: {{ .Name }}</p> <p>Release Date: {{ .ReleasedAt }}</p> <p>Artists: {{ range .Artists }}{{ . }}{{ if not $.Last }}, {{ end }}{{ end }}</p> <p>Album: {{ .Album }}</p>
次に、テンプレートを解析します。
var ( loginTemplate *template.Template ) func init() { var err error loginTemplate, err = template.New("").ParseGlob("./tpls/*.tpl") if err != nil { log.Fatalf("load templates failed:%v", err) } }
対応するページにアクセスするためのロジック:
func PopMusicsHandler(w http.ResponseWriter, r *http.Request) { loginTemplate.ExecuteTemplate(w, "pop_musics.tpl", slcMusics) } func PopMusicHandler(w http.ResponseWriter, r *http.Request) { music, ok := mapMusics[mux.Vars(r)["music_id"]] if!ok { http.NotFound(w, r) return } loginTemplate.ExecuteTemplate(w, "pop_music.tpl", music) }
対応するテンプレートを実行し、音楽リストまたは特定の楽曲の情報を渡します。次に、ログインしているユーザーのみが音楽関連のページにアクセスできるように制限し、ログインしていないユーザーがアクセスしたときにログインページにリダイレクトするミドルウェアを作成します。
func authenticateMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("token") if err!= nil { // no cookie http.Redirect(w, r, "/login", http.StatusFound) return } data, _ := base64.StdEncoding.DecodeString(cookie.Value) values, _ := url.ParseQuery(string(data)) if values.Get("username")!= "user" || values.Get("password")!= "123456" { // failed http.Redirect(w, r, "/login", http.StatusFound) return } next.ServeHTTP(w, r) }) }
次に、ミドルウェア authenticateMiddleware
(ログイン検証が必要) をポップ音楽とロック音楽のサブルートに適用します。ログインサブルートには必要ありません。
func InitPopMusicsRouter(r *mux.Router) { ps := r.PathPrefix("/pop_musics").Subrouter() // ここ ps.Use(authenticateMiddleware) ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) } func InitRockMusicsRouter(r *mux.Router) { rs := r.PathPrefix("/rock_musics").Subrouter() // ここ rs.Use(authenticateMiddleware) rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler) } func InitLoginRouter(r *mux.Router) { ls := r.PathPrefix("/login").Subrouter() ls.Methods("GET").HandlerFunc(login) ls.Methods("POST").HandlerFunc(doLogin) }
プログラムを実行します (複数ファイルのプログラムを実行する方法に注意してください)。
$ go run .
localhost:8080/pop_musics/
にアクセスすると、localhost:8080/login
にリダイレクトされます。ユーザー名 user
とパスワード 123456
を入力すると、ログインに成功するとメインページが表示されます。Cookie
が有効である限り、後続のリクエストを再度検証する必要はありません。
結論
この記事では、軽量で強力なルーティングライブラリ gorilla/mux
を紹介しました。豊富な種類のリクエストマッチングメソッドをサポートしており、サブルートはルート管理を大幅に容易にします。標準ライブラリ net/http
と互換性があるため、net/http
を使用するプログラムにシームレスに統合でき、net/http
用に作成されたミドルウェアリソースを利用できます。次の記事では、gorilla/handlers
— 一般的に使用されるミドルウェアについて説明します。
【Leapcell:最高のサーバーレスウェブホスティング】(https://leapcell.io/)
最後に、Go サービスのデプロイに最適なプラットフォームをお勧めします。Leapcell
🚀 お気に入りの言語で構築
JavaScript、Python、Go、または Rust で簡単に開発できます。
🌍 無制限のプロジェクトを無料でデプロイ
使用量に応じてのみ料金が発生します。リクエストも料金もかかりません。
⚡ 従量課金制、隠れたコストなし
アイドル料金はかからず、シームレスなスケーラビリティのみです。
🔹 Twitter でフォローしてください: @LeapcellHQ