빠른 흐름
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
http.ListenAndServe(":8080", mux)Go HTTP 서버는 프레임워크보다 먼저 Handler 인터페이스와 함수 조합을 읽으면 구조가 잡힙니다.
기본 흐름
handler는 요청 하나를 처리하는 단위다
func health(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}http.HandlerFunc는 이런 함수를 http.Handler처럼 쓸 수 있게 해 줍니다.
mux.HandleFunc("GET /health", health)핵심 시그니처는 항상 같습니다.
func(w http.ResponseWriter, r *http.Request)ResponseWriter: 응답 작성Request: 요청 정보와 context
ServeMux는 라우팅 표면이다
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)ServeMux는 요청 path와 method를 handler로 연결합니다. 작은 서비스나 내부 도구는 표준 ServeMux만으로도 충분한 경우가 많습니다.
"GET /users/{id}"처럼 method와 path를 함께 쓰는 패턴은 Go 1.22 이후의 ServeMux 표면입니다. 더 오래된 Go 버전을 기준으로 유지하는 프로젝트라면 path만 등록하고 method 검사를 handler 안에서 처리해야 합니다.
서버는 timeout과 shutdown을 같이 설계한다
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}실서비스에서는 http.ListenAndServe 한 줄보다 http.Server를 직접 만들고 timeout, shutdown 경계를 잡는 편이 안전합니다.
middleware
middleware는 handler를 받아 handler를 돌려주는 함수다
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Println(r.Method, r.URL.Path, time.Since(start))
})
}이 구조를 알면 인증, 로깅, recover, request id 같은 공통 처리를 handler 바깥에 둘 수 있습니다.
handler := logging(mux)
http.ListenAndServe(":8080", handler)request context는 취소 신호를 담는다
ctx := r.Context()클라이언트 연결이 끊기거나 서버가 요청을 취소하면 request context도 취소될 수 있습니다. DB 호출이나 외부 API 호출에 이 context를 넘기면 요청 수명과 작업 수명을 맞추기 좋습니다.
선택 기준
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 작은 HTTP 서버 | net/http + ServeMux |
| 요청 처리 단위 | http.HandlerFunc |
| 공통 로깅/인증/recover | middleware |
| 운영 서버 timeout | http.Server 직접 구성 |
| 요청 취소 전파 | r.Context() |
주의할 점
Go HTTP 코드는 쉽게 시작할 수 있지만, 운영 서버라면 timeout과 shutdown을 생략하지 않는 편이 좋습니다. handler 안에서는 응답을 여러 번 쓰거나, body close를 빼먹거나, request context를 무시하는 실수가 자주 나옵니다. 작은 서버라도 요청 수명과 공통 middleware 경계를 먼저 잡아 두면 확장이 훨씬 편합니다.
참고 링크
2 sources