本記事で書くこと
※本記事はこちらの記事の続きです。
前回作成したフレームワークにミドルウェアを対応させる手順とについて記載します。
ミドルウェアとはそもそも何なのか
ミドルウェアとは、HTTPリクエストの前後で特定の処理を行う仕組みのことです。
どのリクエストに対しても共通で実行する処理を切り出すことでコードの重複をなくし、シンプルにすることが可能です。また一度ミドルウェアのハンドラを作成しておくことで、再利用したりミドルウェア同士を組み合わせて使うこともできます。
ミドルウェアが用いられる例としては以下のようなケースがあります。
- ログ記録
- 認証
- CORS
- バリデーション
ミドルウェア対応手順
現状のフレームワークにミドルウェアを対応させる主な手順は以下の通りです。
- ミドルウェアの定義
- ミドルウェアを保持するフィールドの追加
- ミドルウェアを登録するメソッドの追加
- ミドルウェアを実行するように
ServeHTTP
を修正
1. ミドルウェアを定義
始めに、ミドルウェアを定義します。
以下のように引数・戻り値ともにhttp.Handler
であるメソッドをMiddleware
として定義します。
type Middleware func(http.Handler) http.Handler
引数のHandler
はこのミドルウェアの実行後に実行されるハンドラです。
これを受け取って、その前(後)にミドルウェアとして実行する処理を追加したメソッドを返します。
func examppleMildleware(next http.Handler) http.Handler {
return http.HandlerFunc(func (r http.ResponseWriter, req *http.Request) {
// 何らかの処理(例:ログの記録、認証)
fmt.Println("Middleware executed")
// 次のハンドラを呼び出す
next.ServeHTTP(r, req)
})
}
2. ミドルウェアを保持するフィールドの追加
次に、App
構造体にミドルウェアを保持するフィールドを追加します。
// アプリケーション全体を管理する構造体
type App struct {
Router RouterInterface
// 追加:ミドルウェアを保持するリストを追加
Middleware []Middleware
}
Middleware
にリストとしてミドルウェアのメソッドを保持します。
GETやPOSTメソッドが呼ばれたときに、このフィールドで保持しているミドルウェアを実行することで処理を挟み込みます。
3. ミドルウェアを登録するメソッドの追加
2.で追加したフィールドに実際にミドルウェアを登録するメソッドを追加します。
func (a *App) Use(m Middleware) {
a.Middleware = append(a.Middleware, m)
}
ユーザはこのメソッドを使って追加したいミドルウェアを自由に追加できます。
4. ミドルウェアを実行するようにServeHTTP
を修正
最後に、GETやPOSTメソッドが呼ばれたときに、Middleware
フィールドに保持しているミドルウェアを実行するようにServeHTTP
を修正します。
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ミドルウェアを適用
// 最終的にRouterのServeHTTPを呼び出すハンドラ
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
a.Router.ServeHTTP(w, r)
})
// ミドルウェアを逆順に適用
for i := len(a.Middleware) - 1; i >= 0; i-- {
handler = http.HandlerFunc(a.Middleware[i](handler).ServeHTTP)
}
// 最終的なハンドラを実行
handler.ServeHTTP(w, r)
}
挙動確認
実際にミドルウェアを追加して正しく動作するか確認してみます。
他のプロジェクトでサーバを立ててみます。
サーバを立てる手順に関してはこちらをご確認ください。
package main
import (
"log"
"net/http"
fwsample "github.com/k-tsurumaki/fw-sample"
)
func main() {
app := fwsample.New()
// ログを出力するミドルウェア
app.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
})
// 認証を行うミドルウェア
app.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "my-token" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
})
// ルートの登録
// curl -X GET http://localhost:8080/hello
// curl -X GET http://localhost:8080/hello -H "Authorization: my-token"
app.Router.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
// curl -X POST http://localhost:8080/echo -d "Hello, Echo!"
// curl -X POST http://localhost:8080/echo -H "Authorization: my-token" -d "Hello, Echo!"
app.Router.Post("/echo", func(w http.ResponseWriter, r *http.Request) {
body := make([]byte, r.ContentLength)
r.Body.Read(body)
w.Write(body)
})
// サーバーの起動
log.Println("Starting server on :8080")
if err := app.Run(":8080"); err != nil {
log.Fatal(err)
}
}
今回はログ出力を行うミドルウェアとAuthorization
ヘッダを用いた簡単な認証を行うミドルウェアを追加しました。
まず、Authorization
ヘッダを設定せずにGETリクエストを投げてみます。
$curl -X GET http://localhost:8080/hello
以下のような回答が返ってきました。
Authorization
ヘッダを設定していないため、認証に失敗しました。
Unauthorized
サーバ側ではログが出力されました。
2025/05/04 18:10:32 Request: GET /hello
続いて、Authorization
ヘッダを設定してGETリクエストを投げてみます。
$curl -X GET http://localhost:8080/hello -H "Authorization: my-token"
認証が成功し、期待通りの回答が返ってきました。
Hello, World!
次回はパスパラメータ対応編!