URLに応じた処理(マルチプレクサとハンドラ)

Go標準ライブラリのnet/httpパッケージを利用して、リクエストをURL毎に処理する方法を説明します。 net/httpパッケージには、Webサーバの中核をなす機能として『マルチプレクサ』と『ハンドラ』があります。 それぞれの役割は次の通りです。

  • マルチプレクサ:リクエストを受け付け、URLに対応するハンドラへ転送する
  • ハンドラ:クリエストに応じた処理をして、レスポンスを返却する
+----------+          +--------------------------------------+
|          |          |                                      |
|  Client  |          |  Server                              |
|          |          |                                      |
|          |          |  +-------------+       +----------+  |
|          |          |  |             |       |          |  |
|          | +------> |  | Multiplexer | +-+-> | Handler1 |  |
|          |          |  |             |   |   |          |  |
|          |          |  +-------------+   |   +----------+  |
|          |          |                    |                 |
|          |          |                    |                 |
|          |          |                    |   +----------+  |
|          |          |                    |   |          |  |
|          |          |                    +-> | Handler2 |  |
|          |          |                        |          |  |
|          |          |                        +----------+  |
|          |          |                                      |
+----------+          +--------------------------------------+

マルチプレクサ

マルチプレクサの使い方は2通りあります。

  • net/httpパッケージが用意したデフォルトのマルチプレクサを利用する
  • マルチプレクサを作成する

デフォルトのマルチプレクサ

次はデフォルトのマルチプレクサを利用して、URLに応じた処理をするサンプルです。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // URLに対応する処理を登録
    http.HandleFunc("/", defaultRoute)
    http.HandleFunc("/route1", route1)
    http.HandleFunc("/route2", route2)

    server := http.Server{
        Addr:    ":8080",
        // デフォルトのマルチプレクサを利用する場合はnilとする
        Handler: nil,
    }
    server.ListenAndServe()
}

// リクエストを受け付ける処理(ハンドラ)
func defaultRoute(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is default route.")
}

// リクエストを受け付ける処理(ハンドラ)
func route1(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is /route1.")
}

// リクエストを受け付ける処理(ハンドラ)
func route2(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is /route2.")
}

このプログラムのポイントは次の通りです。

  • http.HandleFunc関数を用いて、マルチプレクサにURLと対応する処理(ハンドラ)を登録する
  • http.ServerHandlerフィールドにnilを指定する

なお、http.HandleFunc関数のURL指定は末尾が/かどうかで振る舞いが変わります。 末尾が/でない場合、URLが完全一致した場合にリクエストが転送されます。 末尾が/である場合、末尾/を除いたURLが前方一致した場合にリクエストが転送されます。

たとえば、http.HandleFunc("/route/", handler)は次のURLに対応します。

  • /route
  • /route/
  • /route/a

Webサーバを起動して、Webブラウザで次のURLにアクセスし、URL毎に表示が変わることを確認します。

  • http://localhost:8080/
  • http://localhost:8080/route1
  • http://localhost:8080/route2

マルチプレクサの作成

次は明示的にマルチプレクサを作成するサンプルです。 デフォルトのマルチプレクサで示したサンプルと同等の処理です。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // マルチプレクサを作成
    mux := http.NewServeMux()

    // URLに対応する処理を登録
    mux.HandleFunc("/", defaultRoute)
    mux.HandleFunc("/route1", route1)
    mux.HandleFunc("/route2", route2)

    server := http.Server{
        Addr:    ":8080",
        // 作成したマルチプレクサを指定
        Handler: mux,
    }
    server.ListenAndServe()
}

// リクエストを受け付ける処理(ハンドラ)
func defaultRoute(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is default route.")
}

// リクエストを受け付ける処理(ハンドラ)
func route1(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is /route1.")
}

// リクエストを受け付ける処理(ハンドラ)
func route2(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is /route2.")
}

デフォルトのマルチプレクサとの違いは次の通りです。

  • http.NewServeMux関数でマルチプレクサを作成する
  • mux.HandleFuncメソッドでマルチプレクサにURLと対応する処理(ハンドラ)を登録する
  • http.ServerHandlerフィールドに作成したマルチプレクサを登録する

デフォルトのマルチプレクサもhttp.NewServeMux関数で作成したマルチプレクサも機能としての違いはないため、 マルチプレクサを作成・設定する処理は現時点で無駄に思えるでしょう。 ではなぜこれを扱ったかというと、より高機能なリクエストの振り分けが必要になり、サードパーティーや自作のマルチプレクサを利用する場合にマルチプレクサの設定が必要になるためです。

ハンドラ

ハンドラの作成方法は2通りあります。

  • ハンドラ(http.Handler型)を実装する
  • ハンドラ関数(http.HandlerFunc型)を実装する

次の2つはハンドラと呼ばれるので、どちらを指すかは文脈で判断してください。

  • ハンドラ(http.Handler型)とハンドラ関数(http.Handler型)の総称
  • ハンドラ(http.Handler型)

ハンドラ(http.Handler型)

ハンドラ(http.Handler型)はServeHTTPメソッドを持つインターフェースです。 引数はリクエストの読み込みとレスポンスの書き込みに使います。

  • 第一引数:HTTPレスポンスを書き込むための情報
  • 第二引数:HTTPリクエストを読み取るための情報

作成したハンドラをマルチプレクサに登録するため、 デフォルトのマルチプレクサではhttp.Handle関数を使います。 マルチプレクサを作成した場合はHandleメソッドを使います。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // ハンドラを登録
    http.Handle("/", handlerA{})
    server := http.Server{
        Addr:    ":8080",
        Handler: nil,
    }
    server.ListenAndServe()
}

type handlerA struct{}

// ハンドラを実装
func (h handlerA) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "handlerA")
}

ハンドラ関数(http.HandlerFunc型)

ハンドラ関数(http.HandlerFunc型)はハンドラ(http.Handler型)のServeHTTPメソッドと同じシグネチャを持つ関数です。

作成したハンドラをマルチプレクサに登録するため、 デフォルトのマルチプレクサではhttp.HandleFunc関数を使います。 マルチプレクサを作成した場合はHandleFuncメソッドを使います。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // ハンドラ関数を登録
    http.HandleFunc("/", handlerFuncA)
    server := http.Server{
        Addr:    ":8080",
        Handler: nil,
    }
    server.ListenAndServe()
}

// ハンドラ関数
func handlerFuncA(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "handlerFuncA")
}

ハンドラを作り込むことで、リクエストの内容に応じて、動的にレスポンスを作成できます。

以降のページで、リクエストとレスポンスについて説明します。