Go 1.22 버전, http.ServeMux
하나면 충분할까요?
안녕하세요, 오늘은 Go 언어 웹 개발에서 중요한 역할을 하는 http.ServeMux
에 대한 이야기를 해볼까 하는데요.
특히 Go 1.22 버전에서 http.ServeMux
가 얼마나 강력해졌는지, 그리고 써드파티 라이브러리 없이도 충분한지 함께 알아보도록 하겠습니다.
Go 1.22, 달라진 점이 뭘까요?
Go 웹 개발을 하다 보면, 좀 더 효율적이고 유연한 라우팅 기능이 필요할 때가 있는데요.
그래서 많은 개발자들이 httprouter
나 gorilla/mux
같은 써드파티 라이브러리를 사용해왔습니다.
하지만 Go 1.22 버전에서는 표준 라이브러리에 있는 http.ServeMux
가 엄청나게 업그레이드됐다는 사실!
알고 계셨나요? 이제는 굳이 써드파티 라이브러리에 의존하지 않아도 될 정도로 기능이 풍부해졌는데요.
Go 1.22 버전에서는 표준 라이브러리인 net/http
패키지의 기본 HTTP 서비스 멀티플렉서의 패턴 매칭 능력을 강화하는 기능이 추가됐습니다.
기존의 http.ServeMux
는 기본적인 경로 매칭 기능만 제공해서 아쉬운 점이 많았는데요.
이번 업데이트로 써드파티 라이브러리와의 기능 차이를 많이 좁혔다고 합니다.
그럼 새로운 멀티플렉서(mux)는 어떻게 사용하는지, REST 서버 예제를 통해 살펴보고, 성능은 어떤지 gorilla/mux
와 비교해볼까요?
새로운 mux
, 이렇게 사용하면 돼요!
gorilla/mux
같은 써드파티 mux/router를 사용해본 경험이 있다면, 새로운 표준 mux 사용법은 아주 익숙하고 쉬울 겁니다.
먼저 공식 문서를 읽어보는 걸 추천하는데요, 설명이 간결하고 명확해서 이해하기 쉬울 거예요.
(I) 기본 사용법 예시
아래 코드는 mux의 새로운 패턴 매칭 기능 몇 가지를 보여줍니다.
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /path/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "got path\n")
})
mux.HandleFunc("/task/{id}/", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "handling task with id=%v\n", id)
})
http.ListenAndServe("localhost:8090", mux)
}
Go 개발 경험이 있는 분들은 아마 두 가지 새로운 기능을 바로 알아차리셨을 텐데요.
- 첫 번째 핸들러에서는 HTTP 메서드(예: GET)가 패턴의 일부로 명시적으로 사용됩니다. 즉, 이 핸들러는
/path/
로 시작하는 경로에 대한 GET 요청에만 응답하고, 다른 HTTP 메서드 요청은 처리하지 않습니다. - 두 번째 핸들러에서는 두 번째 경로 구성 요소인
{id}
에 와일드카드가 포함되어 있습니다. 이전 버전에서는 지원하지 않았던 기능인데요. 이 와일드카드는 단일 경로 구성 요소와 일치할 수 있으며, 핸들러는 요청의PathValue
메서드를 통해 일치하는 값을 가져올 수 있습니다.
다음은 curl 명령어를 사용하여 이 서버를 테스트하는 예시입니다.
$ gotip run sample.go
# 다른 터미널에서 테스트
$ curl localhost:8090/what/
404 page not found
$ curl localhost:8090/path/
got path
$ curl -X POST localhost:8090/path/
Method Not Allowed
$ curl localhost:8090/task/leapcell/
handling task with id=leapcell
테스트 결과에서 볼 수 있듯이, 서버는 /path/
에 대한 POST 요청을 거부하고 GET 요청만 허용합니다(curl은 기본적으로 GET 요청을 사용합니다).
동시에, 요청이 일치하면 id 와일드카드에 해당 값이 할당됩니다. 새로운 ServeMux
의 더 많은 기능 (예: 후행 경로 규칙, {id}
를 사용한 와일드카드 매칭, {$}
로 끝나는 경로의 엄격한 매칭)을 자세히 알아보려면 문서를 참고하는 것이 좋습니다.
(II) 패턴 충돌 처리
이번 업데이트에서는 서로 다른 패턴 간의 충돌 문제에 특히 주목하고 있습니다. 다음은 예시인데요.
mux := http.NewServeMux()
mux.HandleFunc("/task/{id}/status/", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "handling task status with id=%v\n", id)
})
mux.HandleFunc("/task/0/{action}/", func(w http.ResponseWriter, r *http.Request) {
action := r.PathValue("action")
fmt.Fprintf(w, "handling task 0 with action=%v\n", action)
})
서버가 /task/0/status/
에 대한 요청을 받으면, 두 핸들러 모두 이 요청과 일치할 수 있습니다.
새로운 ServeMux
문서에서는 패턴 우선순위 규칙과 잠재적인 충돌을 처리하는 방법을 자세히 설명합니다.
충돌이 발생하면 등록 프로세스에서 panic이 발생합니다. 위 예제의 경우, 다음과 같은 오류 메시지가 표시됩니다.
panic: pattern "/task/0/{action}/" (registered at sample - conflict.go:14) conflicts with pattern "/task/{id}/status/" (registered at sample - conflict.go:10):
/task/0/{action}/ and /task/{id}/status/ both match some paths, like "/task/0/status/".
But neither is more specific than the other.
/task/0/{action}/ matches "/task/0/action/", but /task/{id}/status/ doesn't.
/task/{id}/status/ matches "/task/id/status/", but /task/0/{action}/ doesn't.
이 오류 메시지는 자세하고 실용적인데요. 복잡한 등록 시나리오(특히 소스 코드의 여러 위치에서 패턴이 등록될 때)에서 이러한 세부 정보는 개발자가 충돌 문제를 신속하게 찾고 해결하는 데 도움이 될 수 있습니다.
새로운 mux
로 서버를 만들어볼까요?
Go REST 서버 시리즈에서는 Go에서 여러 가지 방법으로 작업/할 일 목록 애플리케이션을 위한 간단한 서버를 구현했는데요.
첫 번째 부분은 표준 라이브러리를 기반으로 구현했고, 두 번째 부분은 gorilla/mux
라우터를 사용하여 동일한 서버를 다시 구현했습니다.
이제 Go 1.22의 향상된 mux로 이 서버를 다시 구현하는 것은 매우 의미 있는 일이며, gorilla/mux
를 사용한 솔루션과 비교하는 것도 흥미롭습니다.
(I) 패턴 등록 예시
다음은 몇 가지 대표적인 패턴 등록 코드입니다.
mux := http.NewServeMux()
server := NewTaskServer()
mux.HandleFunc("POST /task/", server.createTaskHandler)
mux.HandleFunc("GET /task/", server.getAllTasksHandler)
mux.HandleFunc("DELETE /task/", server.deleteAllTasksHandler)
mux.HandleFunc("GET /task/{id}/", server.getTaskHandler)
mux.HandleFunc("DELETE /task/{id}/", server.deleteTaskHandler)
mux.HandleFunc("GET /tag/{tag}/", server.tagHandler)
mux.HandleFunc("GET /due/{year}/{month}/{day}/", server.dueHandler)
gorilla/mux
예제와 마찬가지로, 여기서는 동일한 경로를 가진 요청이 특정 HTTP 메서드를 사용하여 서로 다른 핸들러로 라우팅됩니다.
이전의 http.ServeMux
를 사용하면 이러한 매처는 요청을 동일한 핸들러로 전달한 다음 핸들러는 요청 메서드를 기반으로 후속 작업을 결정했을 겁니다.
(II) 핸들러 예시
다음은 핸들러의 코드 예시입니다.
func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) {
log.Printf("handling get task at %s\n", req.URL.Path)
id, err := strconv.Atoi(req.PathValue("id"))
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
task, err := ts.store.GetTask(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
renderJSON(w, task)
}
이 핸들러는 req.PathValue("id")
에서 ID 값을 추출하는데요. gorilla/mux
와 비슷한 방식입니다.
하지만 정규식을 사용하여 {id}
가 정수만 일치하도록 지정하지 않았기 때문에 strconv.Atoi
에서 반환되는 오류에 주의해야 합니다.
전반적으로 최종 결과는 gorilla/mux
를 사용한 솔루션과 매우 유사합니다.
기존의 표준 라이브러리 메서드와 비교할 때, 새로운 mux는 더 복잡한 라우팅 작업을 수행할 수 있어서 라우팅 결정을 핸들러 자체에 맡길 필요성을 줄여주고 개발 효율성과 코드 유지 관리성을 향상시켜줍니다.
결론: 이제 어떤 라우터를 선택해야 할까요?
"어떤 라우터 라이브러리를 선택해야 할까요?"는 Go 초보자들이 흔히 묻는 질문인데요.
Go 1.22 출시 후, 이 질문에 대한 답이 바뀔 수도 있을 것 같습니다.
많은 개발자들이 새로운 표준 라이브러리 mux가 자신의 요구 사항을 충족시키기에 충분하다는 것을 알게 될 것이고, 써드파티 패키지에 의존할 필요가 없어질 수도 있습니다.
물론, 일부 개발자들은 익숙한 써드파티 라이브러리를 계속 선택할 텐데요.
그것도 합리적인 선택입니다.
gorilla/mux
와 같은 라우터는 여전히 표준 라이브러리보다 더 많은 기능을 가지고 있습니다.
또한 많은 Go 프로그래머들이 라우터뿐만 아니라 웹 백엔드를 구축하는 데 필요한 추가 도구를 제공하는 Gin과 같은 가벼운 프레임워크를 선택할 겁니다.
결론적으로, Go 1.22에서 표준 라이브러리 http.ServeMux
를 최적화한 것은 의심할 여지 없이 긍정적인 변화입니다.
'Go' 카테고리의 다른 글
[Go 언어 탐구] 슬라이스 용량(Capacity)은 어떻게 늘어날까? append의 비밀 파헤치기 (Go 1.23 기준) (0) | 2025.04.27 |
---|---|
우버(Uber)가 만든 고성능 Go 로깅! Zap(자프) 사용법 완벽 정리 (설치부터 파일 분리, 색상 출력까지) (0) | 2025.04.26 |
Golang 웹 프레임워크 7종 비교분석 (Gin, Echo, Beego, Revel, Fiber, Gorilla Mux, go-zero/rest) (0) | 2025.03.29 |
Go 언어의 난수, 왜 예측 가능할까요? (math/rand vs crypto/rand 깊이 파헤치기) (0) | 2025.03.24 |
Go (고) 언어 채널, 속 시원히 알려줄게!: 작동 방식부터 활용법까지 완벽 분석 (0) | 2025.03.22 |