레슨 9 / 10·4개 토픽
HTTP 서버와 Context
net/http 기본 서버
Go의 net/http 패키지는 HTTP 서버를 쉽게 구축할 수 있게 해줍니다. http.HandleFunc으로 라우트를 등록하고, http.ListenAndServe로 서버를 시작합니다. 외부 프레임워크 없이도 프로덕션급 서버를 만들 수 있는 것이 Go의 강점입니다.
go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Response struct {
Message string `json:"message"`
Status int `json:"status"`
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
resp := Response{
Message: fmt.Sprintf("Hello, %s!", name),
Status: 200,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func main() {
http.HandleFunc("/hello", helloHandler)
fmt.Println("서버 시작: http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}http.Handler 인터페이스와 미들웨어
go
package main
import (
"log"
"net/http"
"time"
)
// 미들웨어 — 요청 로깅
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("[%s] %s 시작", r.Method, r.URL.Path)
next.ServeHTTP(w, r) // 다음 핸들러 호출
log.Printf("[%s] %s 완료 (%v)", r.Method, r.URL.Path, time.Since(start))
})
}
// 미들웨어 — CORS 헤더
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// http.Handler 인터페이스 직접 구현
type apiHandler struct{}
func (h *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API 응답"))
}
func main() {
mux := http.NewServeMux()
mux.Handle("/api", &apiHandler{})
// 미들웨어 체이닝
handler := loggingMiddleware(corsMiddleware(mux))
log.Fatal(http.ListenAndServe(":8080", handler))
}context 패키지
context 패키지는 API 경계와 고루틴 간에 취소 신호, 타임아웃, 요청 범위 값을 전달합니다. HTTP 요청 처리, 데이터베이스 쿼리, 외부 API 호출 등에서 타임아웃과 취소를 관리하는 데 필수적입니다.
go
package main
import (
"context"
"fmt"
"time"
)
// WithCancel — 수동 취소
func withCancelExample() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("작업 취소됨:", ctx.Err())
return
default:
fmt.Println("작업 중...")
time.Sleep(500 * time.Millisecond)
}
}
}()
time.Sleep(2 * time.Second)
cancel() // 고루틴에 취소 신호 전달
time.Sleep(100 * time.Millisecond)
}
// WithTimeout — 자동 타임아웃
func fetchData(ctx context.Context, query string) (string, error) {
select {
case <-time.After(3 * time.Second): // 느린 작업 시뮬레이션
return "결과: " + query, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
// WithValue — 요청 범위 값 전달
func processRequest(ctx context.Context) {
userID := ctx.Value("userID").(string)
fmt.Printf("사용자 %s의 요청 처리 중\n", userID)
}
func main() {
// 2초 타임아웃
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
result, err := fetchData(ctx, "SELECT * FROM users")
if err != nil {
fmt.Println("타임아웃:", err) // context deadline exceeded
} else {
fmt.Println(result)
}
// WithValue 사용
valCtx := context.WithValue(context.Background(), "userID", "user-123")
processRequest(valCtx)
}Graceful Shutdown 패턴
go
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "서버 동작 중")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 서버를 고루틴에서 시작
go func() {
log.Println("서버 시작: :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("서버 에러: %v", err)
}
}()
// 종료 시그널 대기 (Ctrl+C, kill)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("종료 시그널 수신, 정상 종료 시작...")
// 5초 이내에 진행 중인 요청 완료
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("강제 종료: %v", err)
}
log.Println("서버 정상 종료 완료")
}- •
http.HandleFunc()— 경로에 핸들러 함수를 등록 - •
http.ListenAndServe()— HTTP 서버를 시작하고 요청 대기 - •
http.Handler인터페이스 —ServeHTTP메서드를 구현하여 커스텀 핸들러 생성 - •미들웨어 패턴 —
func(http.Handler) http.Handler로 핸들러를 래핑 - •
context.WithCancel()— 수동으로 취소 신호를 보낼 수 있는 컨텍스트 - •
context.WithTimeout()— 지정 시간 후 자동으로 취소되는 컨텍스트 - •
context.WithValue()— 요청 범위 값을 컨텍스트에 첨부
💡
프로덕션 서버에서는 반드시 Graceful Shutdown을 구현하세요. 진행 중인 요청이 갑자기 끊기는 것을 방지합니다. context.WithValue는 요청 ID, 인증 정보 등 요청 범위 메타데이터에만 사용하고, 함수 매개변수를 대체하는 용도로 사용하지 마세요.