1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go入門】Goで自作フレームワークを作りたい③ ~ミドルウェア対応編~

Last updated at Posted at 2025-05-05

本記事で書くこと

※本記事はこちらの記事の続きです。
前回作成したフレームワークにミドルウェアを対応させる手順とについて記載します。

ミドルウェアとはそもそも何なのか

fw-sample-middleware.drawio (1).png

ミドルウェアとは、HTTPリクエストの前後で特定の処理を行う仕組みのことです。

どのリクエストに対しても共通で実行する処理を切り出すことでコードの重複をなくし、シンプルにすることが可能です。また一度ミドルウェアのハンドラを作成しておくことで、再利用したりミドルウェア同士を組み合わせて使うこともできます。

ミドルウェアが用いられる例としては以下のようなケースがあります。

  • ログ記録
  • 認証
  • CORS
  • バリデーション

ミドルウェア対応手順

現状のフレームワークにミドルウェアを対応させる主な手順は以下の通りです。

  1. ミドルウェアの定義
  2. ミドルウェアを保持するフィールドの追加
  3. ミドルウェアを登録するメソッドの追加
  4. ミドルウェアを実行するようにServeHTTPを修正

1. ミドルウェアを定義

始めに、ミドルウェアを定義します。
以下のように引数・戻り値ともにhttp.HandlerであるメソッドをMiddlewareとして定義します。

fw-sample.go
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構造体にミドルウェアを保持するフィールドを追加します。

fw-sample.go
// アプリケーション全体を管理する構造体
type App struct {
	Router RouterInterface
    // 追加:ミドルウェアを保持するリストを追加
	Middleware []Middleware
}

Middlewareにリストとしてミドルウェアのメソッドを保持します。
GETやPOSTメソッドが呼ばれたときに、このフィールドで保持しているミドルウェアを実行することで処理を挟み込みます。

3. ミドルウェアを登録するメソッドの追加

2.で追加したフィールドに実際にミドルウェアを登録するメソッドを追加します。

fw-sample.go
func (a *App) Use(m Middleware) {
	a.Middleware = append(a.Middleware, m)
}

ユーザはこのメソッドを使って追加したいミドルウェアを自由に追加できます。

4. ミドルウェアを実行するようにServeHTTPを修正

最後に、GETやPOSTメソッドが呼ばれたときに、Middlewareフィールドに保持しているミドルウェアを実行するようにServeHTTPを修正します。

fw-sample.go
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)
}

挙動確認

実際にミドルウェアを追加して正しく動作するか確認してみます。

他のプロジェクトでサーバを立ててみます。
サーバを立てる手順に関してはこちらをご確認ください。

server.go
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!

次回はパスパラメータ対応編!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?