From 5eafb9a5cb8b58116501863dd44d01a6afbd9165 Mon Sep 17 00:00:00 2001 From: moha Date: Thu, 9 Apr 2026 18:32:17 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E3=82=B0=E3=83=AC=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=83=95=E3=83=AB=E3=82=B7=E3=83=A3=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=83=80=E3=82=A6=E3=83=B3=E3=81=A8=E3=82=B5=E3=83=BC=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit http.Serverに切り替え、SIGINT/SIGTERM受信時に処理中リクエストの 完了を待ってから停止するグレースフルシャットダウンを実装。 ReadTimeout(10s)/WriteTimeout(30s)/IdleTimeout(60s)も設定。 Refs #13 --- main.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index b4f58e8..7860a12 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,14 @@ package main import ( + "context" + "errors" "log/slog" "net/http" "os" + "os/signal" + "syscall" + "time" ) // getEnv は環境変数を取得し、未設定の場合はデフォルト値を返す。 @@ -20,9 +25,35 @@ func main() { registerRoutes(mux, store) addr := ":" + getEnv("PORT", "8080") - slog.Info("BringItサーバーを起動しました", "addr", "http://localhost"+addr) - if err := http.ListenAndServe(addr, mux); err != nil { - slog.Error("サーバーの起動に失敗しました", "error", err) + srv := &http.Server{ + Addr: addr, + Handler: requestLogger(mux), + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 60 * time.Second, + } + + // シグナルを受信してグレースフルシャットダウンするチャネル + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + go func() { + slog.Info("BringItサーバーを起動しました", "addr", "http://localhost"+addr) + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + slog.Error("サーバーの起動に失敗しました", "error", err) + os.Exit(1) + } + }() + + <-quit + slog.Info("シャットダウンシグナルを受信しました") + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + slog.Error("グレースフルシャットダウンに失敗しました", "error", err) os.Exit(1) } + slog.Info("サーバーを正常に停止しました") } From 6e39f347524978608045f1f0e247f22c4b92ac3b Mon Sep 17 00:00:00 2001 From: moha Date: Thu, 9 Apr 2026 18:32:24 +0000 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E3=83=AA=E3=82=AF=E3=82=A8?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=AD=E3=82=AE=E3=83=B3=E3=82=B0=E3=83=9F?= =?UTF-8?q?=E3=83=89=E3=83=AB=E3=82=A6=E3=82=A7=E3=82=A2=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 各HTTPリクエストのメソッド・パス・ステータスコード・所要時間・ リモートアドレスをslogで出力するミドルウェアを追加。 Refs #13 --- handler.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/handler.go b/handler.go index da49041..9b638ab 100644 --- a/handler.go +++ b/handler.go @@ -9,6 +9,33 @@ import ( "time" ) +// responseWriter はステータスコードを記録するためのラッパー。 +type responseWriter struct { + http.ResponseWriter + statusCode int +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.statusCode = code + rw.ResponseWriter.WriteHeader(code) +} + +// requestLogger はリクエストのメソッド・パス・ステータス・所要時間をログ出力するミドルウェア。 +func requestLogger(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK} + next.ServeHTTP(rw, r) + slog.Info("リクエスト処理", + "method", r.Method, + "path", r.URL.Path, + "status", rw.statusCode, + "duration", time.Since(start).String(), + "remote", r.RemoteAddr, + ) + }) +} + // startTime はサーバー起動時刻を記録する。 var startTime = time.Now() From 092ab10821d684d9da08fe12000d1ffcbf35f239 Mon Sep 17 00:00:00 2001 From: moha Date: Thu, 9 Apr 2026 18:33:02 +0000 Subject: [PATCH 3/3] =?UTF-8?q?test:=20=E3=83=9F=E3=83=89=E3=83=AB?= =?UTF-8?q?=E3=82=A6=E3=82=A7=E3=82=A2=E3=81=A8responseWriter=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit requestLoggerミドルウェアのリクエスト転送テストと responseWriterのステータスコード記録テストを追加。 Refs #13 --- handler_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/handler_test.go b/handler_test.go index fdf77a0..dabde8f 100644 --- a/handler_test.go +++ b/handler_test.go @@ -350,3 +350,26 @@ func TestIndexPageNotFound(t *testing.T) { t.Fatalf("expected 404 for unknown path, got %d", w.Code) } } + +func TestRequestLoggerMiddleware(t *testing.T) { + mux, _ := setupTestServer() + handler := requestLogger(mux) + + req := httptest.NewRequest("GET", "/health", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("expected 200 through middleware, got %d", w.Code) + } +} + +func TestResponseWriterStatusCode(t *testing.T) { + w := httptest.NewRecorder() + rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK} + + rw.WriteHeader(http.StatusNotFound) + if rw.statusCode != http.StatusNotFound { + t.Fatalf("expected statusCode=404, got %d", rw.statusCode) + } +}