【Go】net/httpパッケージで使えるミドルウェアの作成例
概要
ミドルウェアの仕組みを確認するために、net/httpパッケージを使ってサンプルを作成したので、以下にその実装例を記載します。
ミドルウェアの作成例
使用するサーバのサンプル
次のサンプル実装では、/home
というエンドポイントに対してリクエストを送信すると、 {"message":"Hello World","status":"OK"}
が返されます。
このサーバに対してミドルウェアを適用する例を考えます。
package main
import (
"encoding/json"
"log"
"net/http"
)
type Response struct {
Message string `json:"message"`
Status string `json:"status"`
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
response := Response{
Message: "Hello World",
Status: "OK",
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /home", homeHandler)
log.Println("Server starting on :8080...")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
ミドルウェアが備える機能
作成するミドルウェアはつぎのことができるようにします。
- 複数のミドルウェアを適用可能
サンプルミドルウェア
次の関数は、複数のミドルウェアを適用するための関数です。引数で受け取った可変長のミドルウェアを順番に適用します。
type Middleware func(http.HandlerFunc) http.HandlerFunc
func ApplyMiddlewares(middlewares ...Middleware) func(http.HandlerFunc) http.HandlerFunc {
return func(handler http.HandlerFunc) http.HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
}
ミドルウェアは次のようになります。
func LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
next(w, r)
log.Printf("Request processed in %v", time.Since(start))
}
}
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("Check Auth")
apiKey := r.Header.Get("X-API-Key")
if apiKey == "" {
log.Println("Auth failed: missing API key")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
log.Println("Auth passed")
next(w, r)
}
}
ミドルウェアを適用したサーバ
mux.HandleFuncに対して、ApplyMiddlewaresを適用しています。
この例では、「LoggingMiddleware → AuthMiddleware → homeHandler」の順番にミドルウェアとハンドラが実行されます。
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /home", ApplyMiddlewares(
LoggingMiddleware,
AuthMiddleware,
)(homeHandler))
log.Println("Server starting on :8080...")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
実行イメージ
mux.HandleFuncの第二引数にApplyMiddlewaresを用いている理由
サーバ起動時に、mux.HandleFuncに渡される第二引数は、つぎのようなイメージでラップされた関数です。
ServerMux.HandleFuncの第二引数のhandlerの型と、httpパッケージのHandlerFuncのシグネチャはつぎのようになっています。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
type HandlerFunc func(ResponseWriter, *Request)
一方で、本記事で作成したApplyMiddlewaresは、関数を返す関数となっています。
func ApplyMiddlewares(middlewares ...Middleware) func(http.HandlerFunc) http.HandlerFunc
ApplyMiddlewaresが実行されたときに返ってきた関数を、更に(homeHandler)
という形で実行しており、その結果はhttp.HandlerFuncとなるため、ServerMux.HandleFuncの第二引数と一致するため、ApplyMiddlewaresを使用した形で記述することができています。
ラップされたミドルウェアが順次実行されている理由
各ミドルウェアは next(w, r)
を実行するようになっているため、自身の処理を実行しつつ、ラップされているミドルウェアを呼び出すようになっています。
また、ミドルウェアは最終的にハンドラーを呼び出しています。このため、nextの前の処理は、ハンドラがリクエストを処理する前、nextの後の処理ではハンドラがリクエストを処理した後、に対応することになります。
ミドルウェアのグループ化
つぎのwithAuth
のようにミドルウェアをグループ化して再利用することもできます。
func main() {
mux := http.NewServeMux()
withAuth := ApplyMiddlewares(LoggingMiddleware, AuthMiddleware)
mux.HandleFunc("GET /status", withAuth(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}))
}
ミドルウェアを適用したコードの全体像
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Response struct {
Message string `json:"message"`
Status string `json:"status"`
}
type Middleware func(http.HandlerFunc) http.HandlerFunc
func ApplyMiddlewares(middlewares ...Middleware) func(http.HandlerFunc) http.HandlerFunc {
return func(handler http.HandlerFunc) http.HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
}
func LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
next(w, r)
log.Printf("Request processed in %v", time.Since(start))
}
}
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("Check Auth")
apiKey := r.Header.Get("X-API-Key")
if apiKey == "" {
log.Println("Auth failed: missing API key")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
log.Println("Auth passed")
next(w, r)
}
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
response := Response{
Message: "Hello World",
Status: "OK",
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /home", ApplyMiddlewares(
LoggingMiddleware,
AuthMiddleware,
)(homeHandler))
log.Println("Server starting on :8080...")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
Discussion