ミドルウェア
読了目安 約3分
ハンドラを受け取ってハンドラを返す関数というパターンで、全ハンドラ共通の処理を 1 箇所に書く方法を学びます。
この章の目次
すべてのリクエストについて、どのパスに何秒かかったかをログに残したいとします。 各ハンドラに計測コードを書くと、ハンドラが 30 個なら 30 回書き、形式を変えるときも 30 箇所を直すことになります。 この「全ハンドラに共通する処理を 1 箇所に書く場所」がミドルウェアです。
ハンドラを受け取ってハンドラを返す関数
ルーティングとハンドラで見たとおり、サーバーはリクエストのたびに、渡された 1 つの http.Handler に処理を丸投げします。
そこで、本来のハンドラを包んで「前後に仕事を足した新しいハンドラ」を作り、サーバーにはそちらを渡します。
次の内容を main.go に保存してください。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r) // 本来のハンドラを呼ぶ
log.Printf("%s %s (%v)", r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello!")
})
mux.HandleFunc("GET /slow", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(2 * time.Second)
fmt.Fprintln(w, "お待たせしました")
})
log.Fatal(http.ListenAndServe(":8080", withLogging(mux)))
}go run main.go で起動し、別のターミナルから両方のパスを叩きます。
curl http://localhost:8080/hello
curl http://localhost:8080/slowサーバー側のターミナルに、次のようなログが出ます。
2026/07/17 10:00:01 GET /hello (312.5µs)
2026/07/17 10:00:05 GET /slow (2.001s)どちらのハンドラにも計測のコードはないのに、両方が記録されました。
仕組みを読む
withLogging は、ハンドラを受け取ってハンドラを返す関数です。
返すのは「時刻を控え、預かった next を呼び、経過時間をログに書く」新しいハンドラで、関数のハンドラ化には前々章のアダプタ http.HandlerFunc を使っています。
効かせ方は最後の 1 行で、ListenAndServe に渡すものを mux から withLogging(mux) に差し替えました。
ServeMux もハンドラの一種なので、まるごと包めます。
入口が「ログを書いてから mux に渡す」ものに変わり、mux の先の全ハンドラに計測が効きます。
前後に挟めるという構造
リクエストの通り道は次のとおりで、ハンドラの前後の両方に処理を挟めます。
リクエスト
│
▼
┌─ withLogging ─────────────┐
│ 前処理: 開始時刻を控える │
│ │ │
│ ▼ │
│ next.ServeHTTP │
│ (ServeMux → 各ハンドラ) │
│ │ │
│ ▼ │
│ 後処理: 経過時間をログに書く │
└───────────────────────────┘
│
▼
レスポンスnext.ServeHTTP(w, r) より前のコードはハンドラの実行前に、後のコードは実行後に動きます。
next を呼ばない選択もできます。
たとえば認証のミドルウェアは、正しい認証情報がなければ 401 Unauthorized を返して return し、本来のハンドラには触らせません。
ミドルウェアは重ねられます。
withAuth(withLogging(mux)) と包めば、リクエストは外側から順に認証、ログ、ハンドラと通過します。
関数合成と同じ要領です。
何をミドルウェアに置くか
向いているのは、「どのハンドラか」に依存しない処理です。
- アクセスログ
- 認証の確認
- パニック回復(ハンドラ内の
panicを捕まえ、整った 500 レスポンスを返す)
特定のパスだけの都合は、そのハンドラ自身に書きます。
ISUCON の第一歩「どのエンドポイントが遅いか」の計測も、この形のアクセスログで仕込むのが定番です。 ログを集計する手順は推測するな、計測せよで扱います。 次章のフレームワークにも、これらのミドルウェアが既製品として揃っています。