ミドルウェアとインターセプターの組み合わせ
堅牢で保守性の高いサービスを作成するために、HTTPミドルウェアとGoaインターセプターを組み合わせる強力なパターンを学びます。
GoaサービスのHTTPミドルウェアは、ロギング、メトリクス、トレーシング、リクエストコンテキスト管理など、 プロトコルレベルの関心事を処理します。このガイドでは、Goaサービスでミドルウェアを効果的に使用する 方法を説明します。
HTTPミドルウェアはHTTPハンドラーをラップして処理チェーンを形成します。各ミドルウェアは、リクエストが 処理される前後でアクションを実行できます:
func ExampleMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 前処理
// 例:ロギング、メトリクス、トレーシング
next.ServeHTTP(w, r)
// 後処理
// 例:レスポンスのロギング、クリーンアップ
})
}
典型的なGoaサービスは以下のミドルウェアスタックを使用します:
// ベースとなるHTTPハンドラーを作成
handler := mux
// 標準的なミドルウェアチェーンを追加
handler = debug.HTTP()(handler) // デバッグログ制御
handler = otelhttp.NewHandler(handler, "service") // OpenTelemetry計装
handler = log.HTTP(ctx)(handler) // リクエストログ
handler = goahttpmiddleware.RequestID()(handler) // リクエストID生成
handler = goahttpmiddleware.PopulateRequestContext()(handler) // Goaコンテキスト設定
ロギング、メトリクス、トレーシングを処理します:
// パスフィルタリング付きのロギングミドルウェア
handler = log.HTTP(ctx,
log.WithPathFilter(regexp.MustCompile(`^/(healthz|metrics)$`)))(handler)
// OpenTelemetryトレーシングミドルウェア
handler = otelhttp.NewHandler(handler, "service-name",
otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents))
リクエストコンテキストに有用な情報を追加します:
func ContextEnrichmentMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// リクエストスコープの値を追加
ctx := r.Context()
ctx = context.WithValue(ctx, "request.start", time.Now())
ctx = context.WithValue(ctx, "request.id", r.Header.Get("X-Request-ID"))
// 強化されたコンテキストで続行
next.ServeHTTP(w, r.WithContext(ctx))
})
}
認証とリクエスト検証を処理します:
func SecurityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// セキュリティヘッダーを設定
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1; mode=block")
next.ServeHTTP(w, r)
})
}
ミドルウェアを外側から内側へと慎重に順序付けします:
パフォーマンスのためにミドルウェアを最適化します:
func OptimizedMiddleware(next http.Handler) http.Handler {
// 高コストなオブジェクトを事前コンパイル
pathRegex := regexp.MustCompile(`^/api/v\d+/`)
// 頻繁にアロケーションされるオブジェクトにsync.Poolを使用
bufPool := sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// マッチしないパスの場合はミドルウェアをスキップ
if !pathRegex.MatchString(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
// プールされたリソースを使用
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
next.ServeHTTP(w, r)
})
}
一貫したエラー処理を行います:
func ErrorHandlingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// リクエストコンテキストとともにエラーをログ
log.Printf("パニックを回復: %v", err)
// 500レスポンスを返す
http.Error(w, "内部サーバーエラー", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
Goaサービスとミドルウェアを統合する際:
func main() {
// 1. ベースとなるマルチプレクサを作成
mux := goahttp.NewMuxer()
// 2. Goaサーバーを作成してマウント
server := genhttp.New(endpoints, mux, decoder, encoder, eh, eh)
genhttp.Mount(mux, server)
// 3. ミドルウェアスタックを追加
handler := mux
handler = debug.HTTP()(handler) // デバッグログ
handler = otelhttp.NewHandler(handler, "svc") // トレーシング
handler = log.HTTP(ctx)(handler) // リクエストログ
handler = goahttpmiddleware.RequestID()(handler) // リクエストID
// 4. タイムアウト付きのHTTPサーバーを作成
httpServer := &http.Server{
Addr: ":8080",
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
}
ミドルウェアを単独で、およびチェーンの一部としてテストします:
func TestMiddleware(t *testing.T) {
// テストハンドラーを作成
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// ミドルウェアを追加
handler = YourMiddleware(handler)
// テストリクエストを作成
req := httptest.NewRequest("GET", "/test", nil)
rec := httptest.NewRecorder()
// テスト実行
handler.ServeHTTP(rec, req)
// 結果を検証
if rec.Code != http.StatusOK {
t.Errorf("ステータス %d を取得、期待値は %d", rec.Code, http.StatusOK)
}
}
HTTPミドルウェアは、Goaサービスでプロトコル固有の関心事を処理するための強力なツールです。 これらのパターンとベストプラクティスに従うことで、クリーンで保守性が高く、効率的なHTTP処理 パイプラインを作成できます。