Go と Gorilla/Mux で始める Web 開発完全ガイド


[![Group1161.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/f31ee8e8-651a-4ee5-8b0d-2967b779fc09.png)](https://leapcell.io/)


**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**

### はじめに
`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` ライブラリのインストール**：
```bash
go get -u github.com/gorilla/gorilla/mux
```

次に、音楽情報を管理する `Web` サービスを書きます。各音楽は `MusicID` で一意に識別されます。
1. **音楽の構造を定義する**：
```go
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
)
```
2. **`init()` 関数を定義して、ファイルからデータを読み込む**：
```go
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
    }
}
```
3. **2つのハンドラ関数を定義する。1つは全体のリストを返すもので、もう1つは特定の1曲の音楽を返すもの**：
```go
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)
}
```
4. **ハンドラを登録する**：
```go
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}` で表すことができます）を持っていると仮定します。変数名の後に `:` を追加して、変数と正規表現を区切ります：
```go
r.HandleFunc("/musics/{music_id:M\\d{3}-\\d{3}}", MusicHandler)
```

### 柔軟なマッチング方法
`mux` は、リクエストをマッチングするための豊富な方法を提供しています。これに対して、`net/http` は特定のパスを指定することしかできず、やや拙いです。
- **ルートのドメイン名またはサブドメイン名を指定する**：
```go
r.Host("musicplatform.com")
r.Host("{subdomain:[a-zA-Z0-9]+}.musicplatform.com")
```
上記のルートは、ドメイン名 `musicplatform.com` またはそのサブドメインからのリクエストのみを受け付けます。ドメイン名を指定する際には正規表現を使用することができます。2行目のコードでは、サブドメインの最初の部分がいくつかの文字または数字であることを制限しています。
- **パスプレフィックスを指定する**：
```go
// パスプレフィックス `/musics/` を持つリクエストのみを処理する
r.PathPrefix("/musics/")
```
- **リクエストメソッドを指定する**：
```go
// GET/POST リクエストのみを処理する
r.Methods("GET", "POST")
```
- **使用されるプロトコル (`HTTP`/`HTTPS`)**：
```go
// https リクエストのみを処理する
r.Schemes("https")
```
- **ヘッダー**：
```go
// ヘッダー X-Requested-With の値が XMLHTTPRequest であるリクエストのみを処理する
r.Headers("X-Requested-With", "XMLHTTPRequest")
```
- **クエリパラメータ（つまり、`URL` の `?` 以降の部分）**：
```go
// クエリパラメータが key=value を含むリクエストのみを処理する
r.Queries("key", "value")
```
- **複合条件**：
```go
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` のリクエストのみを処理する必要がある場合、次のように書くことができます：
```go
r.MatchrFunc(func(r *http.Request, rm *RouteMatch) bool {
    return r.ProtoMajor == 1 && r.ProtoMinor == 1
})
```

`mux` はルート登録の順序でマッチングを行うことに注意すべきです。したがって、通常は特殊なルートを前に、一般的なルートを後に配置することがおすすめです。逆の場合、特殊なルートがマッチングされなくなります：
```go
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)
```

### サブルート
時々、ルートをグルーピングして管理することで、プログラムのモジュールがより明確になり、保守しやすくなります。ウェブサイトが事業を拡大し、異なる種類の音楽（例えば、ポップ、ロックなど）に関連する情報を追加したと仮定します。私たちは複数のサブルートを定義して、個別に管理することができます：
```go
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()` メソッドを定義して、ポップ音楽に関連するルートの登録を担当させます：
```go
func InitPopMusicsRouter(r *mux.Router) {
    ps := r.PathPrefix("/pop_musics").Subrouter()
    ps.HandleFunc("/", PopMusicsHandler)
    ps.HandleFunc("/{music_id}", PopMusicHandler)
}
```

`rock_music.go` ファイルに `InitRockMusicsRouter()` メソッドを定義して、ロック音楽に関連するルートの登録を担当させます：
```go
func InitRockMusicsRouter(r *mux.Router) {
    rs := r.PathPrefix("/rock_musics").Subrouter()
    rs.HandleFunc("/", RockMusicsHandler)
    rs.HandleFunc("/{music_id}", RockMusicHandler)
}
```

``main.go` のメイン関数内では：
```go
func main() {
    r := mux.NewRouter()
    InitPopMusicsRouter(r)
    InitRockMusicsRouter(r)

    http.Handle("/", r)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

注意すべきは、サブルートのマッチングにはパスプレフィックスを含める必要があります。つまり、`/pop_musics/` が `PopMusicsHandler` をマッチングすることができます。

### ルート `URL` の構築
私たちはルートに名前を付けることができます。例えば：
```go
r.HandleFunc("/musics/{music_id}", MusicHandler).Name("music")
```

上記のルートにはパラメータがあります。私たちはパラメータ値を渡して、完全なパスを構築することができます：
```go
fmt.Println(r.Get("music").URL("music_id", "M001-001"))
// /musics/M001-001 <nil>
```

返されるのは `*url.URL` オブジェクトで、そのパス部分は `/musics/M001-001` です。これはホスト名やクエリパラメータにも適用されます：
```go
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")
```

パス内のすべてのパラメータを指定する必要があり、値は指定された正規表現（ある場合）を満たす必要があります。実行結果は以下の通りです：
```bash
$ go run main.go
http://user1.musicplatform.com/musics/M001-001
```

`URLHost()` を呼び出してホスト名部分のみを生成し、`URLPath()` を呼び出してパス部分のみを生成することができます。

### ミドルウェア
`mux` はミドルウェアの型 `MiddlewareFunc` を定義しています：
```go
type MiddlewareFunc func(http.Handler) http.Handler
```

この型を満たすすべての関数は、`mux` のミドルウェアとして使用することができます。ミドルウェアは、ルーティングオブジェクト `*mux.Router` の `Use()` メソッドを呼び出すことで適用されます。ミドルウェアを書く際には、通常、元のハンドラを渡します。ミドルウェア内では、元のハンドラ関数を手動で呼び出し、その前後に一般的な処理ロジックを追加します：
```go
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` が生成され、ログイン成功を示します：
```go
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` を使ってパースします：
1. **ログイン表示ページ**：
```html
<!-- 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>
```
2. **メインページ**：
```html
<ul>
    <li><a href="/pop_musics/">Pop Music</a></li>
    <li><a href="/rock_musics/">Rock Music</a></li>
</ul>
```
3. **音楽リストと詳細ページ（例）**：
```html
<!-- 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>
```
```html
<!-- 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>
```

次に、テンプレートをパースします：
```go
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)
    }
}
```

対応するページにアクセスするためのロジック：
```go
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曲の音楽情報を渡します。現在、ログイン済みのユーザのみが音楽関連のページにアクセスできるように制限するミドルウェアを書き、未ログインのユーザがアクセスした際にログインページにリダイレクトするようにします：
```go
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`（ログイン検証が必要なもの）をポップ音楽とロック音楽のサブルートに適用しますが、ログインサブルートには適用しません：
```go
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)
}
```

プログラムを実行します（複数ファイルのプログラムの実行方法に注意してください）：
```bash
$ 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](https://leapcell.io/)

最後に、Go サービスをデプロイするのに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/)

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/1ec29889-ff20-4c3a-95fd-f76ef9956088.png)](http://leapcell.io/?lc_t=q_mux)


### 🚀 好きな言語で構築しましょう  
JavaScript、Python、Go、または Rust で簡単に開発できます。  

### 🌍 無料で無制限のプロジェクトをデプロイ  
使用した分だけ支払います — リクエストがなければ、請求もありません。  

### ⚡ 使った分だけ支払い、隠れた費用はありません  
アイドル料金はなく、シームレスなスケーラビリティが提供されます。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/b68a3914-bd09-47ce-93ff-acbb0d26181b.png)](http://leapcell.io/?lc_t=q_mux)


[📖 ドキュメントを探索する](https://docs.leapcell.io/)  

🔹 Twitter でフォローしてください：[@LeapcellHQ](https://x.com/LeapcellHQ)