0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

Group1161.png

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 で一意に識別されます。

  1. 音楽の構造を定義する
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
)
  1. 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
    }
}
  1. 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)
}
  1. ハンドラを登録する
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.RouterUse() メソッドを呼び出すことで適用されます。ミドルウェアを書く際には、通常、元のハンドラを渡します。ミドルウェア内では、元のハンドラ関数を手動で呼び出し、その前後に一般的な処理ロジックを追加します:

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 を使ってパースします:

  1. ログイン表示ページ
<!-- 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>
  1. メインページ
<ul>
    <li><a href="/pop_musics/">Pop Music</a></li>
    <li><a href="/rock_musics/">Rock Music</a></li>
</ul>
  1. 音楽リストと詳細ページ(例)
<!-- 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

brandpic7.png

🚀 好きな言語で構築しましょう

JavaScript、Python、Go、または Rust で簡単に開発できます。

🌍 無料で無制限のプロジェクトをデプロイ

使用した分だけ支払います — リクエストがなければ、請求もありません。

⚡ 使った分だけ支払い、隠れた費用はありません

アイドル料金はなく、シームレスなスケーラビリティが提供されます。

Frame3-withpadding2x.png

📖 ドキュメントを探索する

🔹 Twitter でフォローしてください:@LeapcellHQ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?