Leapcell: The Best of Serverless Web Hosting
はじめに
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つは特定の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つは特定の1曲の音楽情報を表示するものです。
注意すべきは、パス /musics/{music_id}
で変数を使用していることです。{}
の中に変数名を指定します。これにより、パスの特定の部分をマッチングすることができます。ハンドラ関数内では、mux.Vars(r)
を通じてリクエスト 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)
サブルート
時々、ルートをグルーピングして管理することで、プログラムのモジュールがより明確になり、保守しやすくなります。ウェブサイトが事業を拡大し、異なる種類の音楽(例えば、ポップ、ロックなど)に関連する情報を追加したと仮定します。私たちは複数のサブルートを定義して、個別に管理することができます:
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)
})
}
ウェブサイトが音楽関連のページにアクセスするためにログインを必要とすると仮定します。私たちはこのロジックを処理するためのミドルウェアを書くことができます。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)
}
対応するテンプレートを実行し、音楽リストまたは特定の1曲の音楽情報を渡します。現在、ログイン済みのユーザのみが音楽関連のページにアクセスできるように制限するミドルウェアを書き、未ログインのユーザがアクセスした際にログインページにリダイレクトするようにします:
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 {
// 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" {
// 失敗
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: The Best of Serverless Web Hosting
最後に、Go サービスをデプロイするのに最適なプラットフォームをおすすめします:**Leapcell
🚀 好きな言語で構築しましょう
JavaScript、Python、Go、または Rust で簡単に開発できます。
🌍 無料で無制限のプロジェクトをデプロイ
使用した分だけ支払います — リクエストがなければ、請求もありません。
⚡ 使った分だけ支払い、隠れた費用はありません
アイドル料金はなく、シームレスなスケーラビリティが提供されます。
🔹 Twitter でフォローしてください:@LeapcellHQ