빠른 설정
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20,
}운영 HTTP 서버는 handler 로직보다 먼저 연결, header, body, 응답 쓰기 시간의 상한을 잡아야 합니다.
timeout 경계
ReadHeaderTimeout은 느린 header 전송을 막는다
ReadHeaderTimeout은 서버가 요청 header를 읽는 데 허용할 시간을 제한합니다. 느린 클라이언트가 연결만 잡고 header를 천천히 보내면 goroutine과 connection 자원이 오래 묶일 수 있습니다. body를 읽는 시간까지 넓게 제한하고 싶으면 ReadTimeout을 함께 봐야 하지만, header 단계만 빠르게 막고 싶을 때는 ReadHeaderTimeout이 더 직접적입니다.
ReadTimeout과 WriteTimeout은 요청 전체와 응답 쓰기 경계다
ReadTimeout은 요청 header와 body를 읽는 쪽의 상한으로 보고, WriteTimeout은 응답을 쓰는 쪽의 상한으로 봅니다. JSON API처럼 body가 작고 처리 시간이 예측 가능하면 전체 상한을 비교적 짧게 둘 수 있습니다. 반대로 파일 업로드나 streaming 응답은 단순한 timeout 값만으로 처리하면 정상 요청까지 끊을 수 있으므로 endpoint 성격을 나눠 설계해야 합니다.
func upload(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
defer r.Body.Close()
// decode multipart or JSON here
}body 크기는 handler에서 명시적으로 제한한다
http.MaxBytesReader는 들어오는 request body 크기를 제한하는 데 쓰입니다. reverse proxy나 load balancer에서 body size를 막아도, 애플리케이션 handler에서도 한 번 더 제한하는 편이 안전합니다. 특히 json.Decoder, io.ReadAll, multipart parsing처럼 body를 읽는 코드 앞에 제한을 두어야 큰 body가 메모리와 CPU를 과도하게 쓰는 일을 줄일 수 있습니다.
어디서 제한할까
| 제한 대상 | Go 표면 |
|---|---|
| header를 천천히 보내는 요청 | ReadHeaderTimeout |
| header와 body 읽기 전체 | ReadTimeout |
| 응답 쓰기 지연 | WriteTimeout |
| keep-alive idle connection | IdleTimeout |
| header 크기 | MaxHeaderBytes |
| request body 크기 | http.MaxBytesReader |
주의할 점
timeout을 전부 같은 값으로 맞추면 endpoint 성격을 반영하기 어렵습니다. 작은 JSON API, 파일 업로드, streaming 응답은 읽기와 쓰기 시간이 다르므로 서버 공통 timeout과 handler 단위 제한을 함께 설계해야 합니다.
// 위험: body 제한 없이 전체를 메모리에 읽음
body, err := io.ReadAll(r.Body)
if err != nil {
return
}
_ = body큰 요청을 받을 수 있는 handler라면 ReadAll이나 decoder 호출 전에 body size limit을 먼저 둡니다.
참고 링크
1 sources