URLに応じた処理(マルチプレクサとハンドラ)
Go標準ライブラリのnet/httpパッケージを利用して、リクエストをURL毎に処理する方法を説明します。
net/httpパッケージには、Webサーバの中核をなす機能として『マルチプレクサ』と『ハンドラ』があります。
それぞれの役割は次の通りです。
- マルチプレクサ:リクエストを受け付け、URLに対応するハンドラへ転送する
- ハンドラ:リクエストに応じた処理をして、レスポンスを返却する
+----------+ +--------------------------------------+
| | | |
| Client | | Server |
| | | |
| | | +-------------+ +----------+ |
| | | | | | | |
| | +------> | | Multiplexer | +-+-> | Handler1 | |
| | | | | | | | |
| | | +-------------+ | +----------+ |
| | | | |
| | | | |
| | | | +----------+ |
| | | | | | |
| | | +-> | Handler2 | |
| | | | | |
| | | +----------+ |
| | | |
+----------+ +--------------------------------------+
マルチプレクサ
マルチプレクサの使い方は2通りあります。
net/httpパッケージが用意したデフォルトのマルチプレクサを利用する- マルチプレクサを作成する
デフォルトのマルチプレクサ
次はデフォルトのマルチプレクサを利用して、URLに応じた処理をするサンプルです。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// URLに対応する処理を登録
http.HandleFunc("/", defaultRoute)
http.HandleFunc("/route1", route1)
http.HandleFunc("/route2", route2)
server := http.Server{
Addr: ":8080",
// デフォルトのマルチプレクサを利用する場合はnilとする
Handler: nil,
}
log.Fatal(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.ServerのHandlerフィールドにnilを指定する
なお、http.HandleFunc関数のURL指定は末尾が/かどうかで振る舞いが変わります。
末尾が/でない場合、URLが完全一致した場合にリクエストが転送されます。
末尾が/である場合、そのURLを前方一致した場合にリクエストが転送されます。
なお、/route(末尾スラッシュなし)へのアクセスは/route/へ301リダイレクトされます。
たとえば、http.HandleFunc("/route/", handler)の動作は次の通りです。
/route→/route/へ301リダイレクト/route/→ ハンドラへ転送/route/a→ ハンドラへ転送
Webサーバを起動して、Webブラウザで次のURLにアクセスし、URL毎に表示が変わることを確認します。
http://localhost:8080/http://localhost:8080/route1http://localhost:8080/route2
マルチプレクサの作成
次は明示的にマルチプレクサを作成するサンプルです。 デフォルトのマルチプレクサで示したサンプルと同等の処理です。
package main
import (
"fmt"
"log"
"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,
}
log.Fatal(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.")
}
この例では、デフォルトのマルチプレクサではなく、自分で作成したmuxを使っています。
違いは次の3点です。
http.NewServeMuxでマルチプレクサを作成するhttp.HandleFuncではなく、mux.HandleFuncでそのマルチプレクサにハンドラを登録するhttp.ServerのHandlerフィールドにnilではなくmuxを指定する
つまり、http.Serverごとに、どのマルチプレクサを使うかを明示できます。
たとえば、外部公開用のサーバには通常のAPI用マルチプレクサを渡し、管理用のサーバにはメトリクスや管理画面用のマルチプレクサを渡せます。
また、標準のServeMuxでは表現しにくい振り分けが必要になったときも、http.Serverに渡すHandlerを変えるだけで、サードパーティ製や自作のマルチプレクサに切り替えられます。
Go 1.22以降のルーティングパターン
Go 1.22以降:
http.ServeMuxはHTTPメソッドとワイルドカードを含むパターンに対応しています。 動的パスの値はRequest.PathValueメソッドで取得できます。
Go 1.22以降では、標準ライブラリだけでHTTPメソッドと動的パスを組み合わせたルーティングを書けます。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /tasks", getTasks)
mux.HandleFunc("POST /tasks", postTask)
mux.HandleFunc("GET /tasks/{id}", getTask)
server := http.Server{
Addr: ":8080",
Handler: mux,
}
log.Fatal(server.ListenAndServe())
}
func getTasks(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "tasks")
}
func postTask(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "created task")
}
func getTask(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "task id: %s", id)
}
Go 1.21以前のhttp.ServeMuxは、HTTPメソッドや{id}形式のワイルドカードを標準では扱いません。
その場合は、パスを自分で解析するか、サードパーティのルータや自作のマルチプレクサを利用します。
ハンドラ
ハンドラの作成方法は2通りあります。
- ハンドラ(
http.Handler型)を実装する - ハンドラ関数(
http.HandlerFunc型)を実装する
次の2つはハンドラと呼ばれるので、どちらを指すかは文脈で判断してください。
- ハンドラ(
http.Handler型)とハンドラ関数(http.HandlerFunc型)の総称 - ハンドラ(
http.Handler型)
ハンドラ(http.Handler型)
ハンドラ(http.Handler型)はServeHTTPメソッドを持つインターフェースです。
引数はリクエストの読み込みとレスポンスの書き込みに使います。
- 第一引数:HTTPレスポンスを書き込むための情報
- 第二引数:HTTPリクエストを読み取るための情報
作成したハンドラをマルチプレクサに登録するため、
デフォルトのマルチプレクサではhttp.Handle関数を使います。
マルチプレクサを作成した場合はHandleメソッドを使います。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// ハンドラを登録
http.Handle("/", handlerA{})
server := http.Server{
Addr: ":8080",
Handler: nil,
}
log.Fatal(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"
"log"
"net/http"
)
func main() {
// ハンドラ関数を登録
http.HandleFunc("/", handlerFuncA)
server := http.Server{
Addr: ":8080",
Handler: nil,
}
log.Fatal(server.ListenAndServe())
}
// ハンドラ関数
func handlerFuncA(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "handlerFuncA")
}
ハンドラを作り込むことで、リクエストの内容に応じて、動的にレスポンスを作成できます。
以降のページで、リクエストとレスポンスについて説明します。