diff --git a/vehicle-data-platform/apps/api/internal/app/server.go b/vehicle-data-platform/apps/api/internal/app/server.go index e04a8541..fc503f72 100644 --- a/vehicle-data-platform/apps/api/internal/app/server.go +++ b/vehicle-data-platform/apps/api/internal/app/server.go @@ -35,5 +35,12 @@ func NewServer(cfg config.Config) http.Handler { } } api := platform.NewHandler(platform.NewService(store)) - return static.Handler(cfg.StaticDir, api) + return withRequestTimeout(static.Handler(cfg.StaticDir, api), cfg.RequestTimeout) +} + +func withRequestTimeout(next http.Handler, timeout time.Duration) http.Handler { + if timeout <= 0 { + return next + } + return http.TimeoutHandler(next, timeout, `{"error":{"code":"REQUEST_TIMEOUT","message":"请求处理超时"},"traceId":"timeout","timestamp":0}`) } diff --git a/vehicle-data-platform/apps/api/internal/app/server_test.go b/vehicle-data-platform/apps/api/internal/app/server_test.go new file mode 100644 index 00000000..f1b5a1e9 --- /dev/null +++ b/vehicle-data-platform/apps/api/internal/app/server_test.go @@ -0,0 +1,29 @@ +package app + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestWithRequestTimeoutAddsContextDeadline(t *testing.T) { + handler := withRequestTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + deadline, ok := r.Context().Deadline() + if !ok { + t.Fatal("request context should have deadline") + } + if time.Until(deadline) > 250*time.Millisecond { + t.Fatalf("deadline too far away: %s", time.Until(deadline)) + } + w.WriteHeader(http.StatusNoContent) + }), 200*time.Millisecond) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/ops/health", nil) + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusNoContent { + t.Fatalf("status = %d", rec.Code) + } +} diff --git a/vehicle-data-platform/apps/api/internal/config/config.go b/vehicle-data-platform/apps/api/internal/config/config.go index 8274c46d..04731300 100644 --- a/vehicle-data-platform/apps/api/internal/config/config.go +++ b/vehicle-data-platform/apps/api/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( "os" "strconv" + "time" ) type Config struct { @@ -18,6 +19,7 @@ type Config struct { TDengineDatabase string CapacityCheckBin string AuthToken string + RequestTimeout time.Duration } func Load() Config { @@ -34,6 +36,7 @@ func Load() Config { TDengineDatabase: env("TDENGINE_DATABASE", "lingniu_vehicle_ts"), CapacityCheckBin: os.Getenv("CAPACITY_CHECK_BIN"), AuthToken: os.Getenv("AUTH_TOKEN"), + RequestTimeout: time.Duration(envInt("REQUEST_TIMEOUT_MS", 5000)) * time.Millisecond, } } diff --git a/vehicle-data-platform/apps/api/internal/config/config_test.go b/vehicle-data-platform/apps/api/internal/config/config_test.go new file mode 100644 index 00000000..afc8e14e --- /dev/null +++ b/vehicle-data-platform/apps/api/internal/config/config_test.go @@ -0,0 +1,16 @@ +package config + +import ( + "testing" + "time" +) + +func TestLoadReadsRequestTimeout(t *testing.T) { + t.Setenv("REQUEST_TIMEOUT_MS", "1200") + + cfg := Load() + + if cfg.RequestTimeout != 1200*time.Millisecond { + t.Fatalf("RequestTimeout = %s, want 1.2s", cfg.RequestTimeout) + } +} diff --git a/vehicle-data-platform/docs/deployment.md b/vehicle-data-platform/docs/deployment.md index 0138bea6..e62239f5 100644 --- a/vehicle-data-platform/docs/deployment.md +++ b/vehicle-data-platform/docs/deployment.md @@ -35,6 +35,7 @@ TDENGINE_DSN=root:***@ws(172.17.111.57:6041)/ TDENGINE_DATABASE=lingniu_vehicle_ts CAPACITY_CHECK_BIN=/opt/lingniu-go-native/current/capacity-check AUTH_TOKEN=*** +REQUEST_TIMEOUT_MS=5000 ``` ## Health