diff --git a/cmd/iot-service/main.go b/cmd/iot-service/main.go index 303ae38..12cd2b5 100644 --- a/cmd/iot-service/main.go +++ b/cmd/iot-service/main.go @@ -104,6 +104,11 @@ func run() int { Addr: ":" + cfg.APIPort, Handler: router, ReadHeaderTimeout: 10 * time.Second, + ReadTimeout: 15 * time.Second, + // WriteTimeout с запасом: admin-stats/telemetry отвечают через SQS/PG + // (внутренние таймауты до 15с). IdleTimeout — для keep-alive платформы. + WriteTimeout: 60 * time.Second, + IdleTimeout: 120 * time.Second, } // bridge + consumer — в фоне. diff --git a/internal/service/api/handler/devices.go b/internal/service/api/handler/devices.go index 703a0a0..c4bcc13 100644 --- a/internal/service/api/handler/devices.go +++ b/internal/service/api/handler/devices.go @@ -12,7 +12,6 @@ package handler import ( "crypto/rand" "encoding/hex" - "encoding/json" "errors" "net/http" "regexp" @@ -143,7 +142,7 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) { } var req iotDeviceCreateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + if err := decodeJSON(w, r, &req); err != nil { writeJSON(w, http.StatusBadRequest, errResp("invalid JSON: "+err.Error())) return } @@ -299,7 +298,7 @@ func (h *Handler) UpdateIoTDevice(w http.ResponseWriter, r *http.Request) { name := pathVar(r, "name") var req iotDeviceUpdateRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + if err := decodeJSON(w, r, &req); err != nil { writeJSON(w, http.StatusBadRequest, errResp("invalid JSON: "+err.Error())) return } diff --git a/internal/service/api/handler/handler.go b/internal/service/api/handler/handler.go index 22e817c..cf7c9f5 100644 --- a/internal/service/api/handler/handler.go +++ b/internal/service/api/handler/handler.go @@ -38,6 +38,21 @@ func writeJSON(w http.ResponseWriter, status int, v any) { _ = json.NewEncoder(w).Encode(v) } +// maxRequestBodyBytes — максимум размера тела запроса (устройства/метаданные +// небольшие; 64 КиБ с запасом). +const maxRequestBodyBytes = 64 * 1024 + +// decodeJSON читает JSON-тело с лимитом размера и запретом неизвестных полей +// (ревью 2026-10-10). MaxBytesReader защищает от раздутых тел; +// DisallowUnknownFields ловит опечатки в полях — критично для Terraform (лучше +// 400 с понятной ошибкой, чем молча проигнорированное поле). +func decodeJSON(w http.ResponseWriter, r *http.Request, v any) error { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodyBytes) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + return dec.Decode(v) +} + // errResp возвращает структуру ошибки для JSON. func errResp(msg string) map[string]string { return map[string]string{"error": msg}