chore(iot): лимит тела запроса + запрет неизвестных полей + таймауты сервера (Этап 5)

- decodeJSON: http.MaxBytesReader (64 КиБ) + DisallowUnknownFields; используется
  в Create/Update устройства. Ловит опечатки в полях (важно для Terraform).
- http.Server: добавлены ReadTimeout=15с, WriteTimeout=60с, IdleTimeout=120с
  (был только ReadHeaderTimeout).
This commit is contained in:
Repinoid
2026-10-10 19:14:47 +03:00
parent e429f11e7f
commit 4a0372ff05
3 changed files with 22 additions and 3 deletions
+5
View File
@@ -104,6 +104,11 @@ func run() int {
Addr: ":" + cfg.APIPort, Addr: ":" + cfg.APIPort,
Handler: router, Handler: router,
ReadHeaderTimeout: 10 * time.Second, 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 — в фоне. // bridge + consumer — в фоне.
+2 -3
View File
@@ -12,7 +12,6 @@ package handler
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"net/http" "net/http"
"regexp" "regexp"
@@ -143,7 +142,7 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) {
} }
var req iotDeviceCreateRequest 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())) writeJSON(w, http.StatusBadRequest, errResp("invalid JSON: "+err.Error()))
return return
} }
@@ -299,7 +298,7 @@ func (h *Handler) UpdateIoTDevice(w http.ResponseWriter, r *http.Request) {
name := pathVar(r, "name") name := pathVar(r, "name")
var req iotDeviceUpdateRequest 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())) writeJSON(w, http.StatusBadRequest, errResp("invalid JSON: "+err.Error()))
return return
} }
+15
View File
@@ -38,6 +38,21 @@ func writeJSON(w http.ResponseWriter, status int, v any) {
_ = json.NewEncoder(w).Encode(v) _ = 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. // errResp возвращает структуру ошибки для JSON.
func errResp(msg string) map[string]string { func errResp(msg string) map[string]string {
return map[string]string{"error": msg} return map[string]string{"error": msg}