Go의 `net` 패키지를 사용하여 Gin과 유사한 HTTP 라우터 구현하기
Olivia Novak
Dev Intern · Leapcell

Go의 net
패키지를 사용하여 Gin과 유사한 HTTP 라우터 구현하기
1. 소개
현대 웹 개발에서 효율적이고 유연한 라우팅 시스템은 웹 애플리케이션 구축의 핵심 구성 요소 중 하나입니다. Go 프로그래밍 언어는 높은 성능, 단순성 및 강력한 표준 라이브러리로 인해 웹 개발 분야에서 매우 선호됩니다. Go의 net/http
패키지는 표준 라이브러리에서 HTTP 서버를 구현한 것입니다. 강력하지만 비교적 로우 레벨입니다. Gin과 같은 경량 웹 프레임워크에서처럼 라우팅을 처리하려면 간소화된 라우터를 직접 구현할 수 있습니다. 이 기사에서는 Go의 net
패키지를 사용하여 Gin과 유사한 HTTP 서버를 구현하는 방법에 대한 자세한 소개를 제공합니다. 동시에 HTTP 관련 지식, 일반적인 라우팅 구현 방법 및 이를 기반으로 미들웨어를 구현하는 방법에 대해 자세히 알아볼 것입니다.
2. HTTP 기본 사항 복습
2.1 HTTP 요청 및 응답
HTTP(Hypertext Transfer Protocol)는 하이퍼텍스트 전송에 사용되는 프로토콜이며 웹 애플리케이션의 기본입니다. HTTP 요청은 일반적으로 다음 부분으로 구성됩니다.
- 요청 라인: 요청 방법(
GET
,POST
,PUT
,DELETE
등), 요청된 URI(Uniform Resource Identifier) 및 HTTP 버전을 포함합니다. - 요청 헤더:
User-Agent
,Content-Type
등과 같은 요청에 대한 추가 정보가 포함되어 있습니다. - 요청 본문: 요청 데이터가 포함되어 있으며 일반적으로
POST
또는PUT
요청에 사용됩니다.
HTTP 응답도 여러 부분으로 구성됩니다.
- 상태 라인: HTTP 버전, 상태 코드(예:
200
은 성공,404
는 찾을 수 없음,500
은 내부 서버 오류 등을 나타냄) 및 상태 메시지를 포함합니다. - 응답 헤더:
Content-Type
,Content-Length
등과 같은 응답에 대한 추가 정보가 포함되어 있습니다. - 응답 본문: HTML 페이지, JSON 데이터 등과 같은 응답 데이터가 포함되어 있습니다.
2.2 HTTP 메서드
일반적인 HTTP 메서드는 다음과 같습니다.
- GET: 리소스를 검색하는 데 사용됩니다.
- POST: 일반적으로 새 리소스를 생성하기 위해 서버에 데이터를 제출하는 데 사용됩니다.
- PUT: 리소스를 업데이트하는 데 사용됩니다.
- DELETE: 리소스를 삭제하는 데 사용됩니다.
서로 다른 HTTP 메서드는 의미가 다르며, 라우팅 시스템을 설계할 때 서로 다른 메서드에 따라 요청을 처리해야 합니다.
3. 일반적인 라우팅 구현 방법
3.1 정적 라우팅
정적 라우팅은 고정된 URL 경로를 특정 핸들러 함수에 매핑하는 가장 간단한 라우팅 방법입니다. 예를 들어 /about
경로를 about 페이지를 표시하는 핸들러 함수에 매핑합니다.
3.2 동적 라우팅
동적 라우팅을 사용하면 URL 경로에 매개변수를 포함할 수 있으며, 이러한 매개변수는 핸들러 함수에서 검색할 수 있습니다. 예를 들어 /users/:id
의 :id
는 특정 사용자에 대한 정보를 검색하는 데 사용할 수 있는 매개변수입니다.
3.3 정규 표현식 라우팅
정규 표현식 라우팅을 사용하면 정규 표현식을 사용하여 URL 경로를 일치시킬 수 있습니다. 이 방법은 더 유연하며 복잡한 라우팅 규칙을 처리할 수 있습니다. 예를 들어 정규 표현식을 사용하여 .html
로 끝나는 모든 URL 경로를 일치시킵니다.
4. 디자인 아이디어
라우팅 기능을 구현하려면 다음이 필요합니다.
- HTTP 요청 경로 및 메서드를 구문 분석합니다.
- 다른 경로 및 메서드에 대한 핸들러 함수를 저장합니다.
- 동적 라우팅 매개변수를 구문 분석합니다.
- 404 오류를 처리합니다.
map
구조체를 사용하여 라우팅 규칙을 저장합니다. 각 경로는 요청을 효율적으로 일치시킬 수 있는 다른 HTTP 메서드에 해당합니다. 특히 중첩된 map
을 사용합니다. 외부 map
의 키는 HTTP 메서드이고, 내부 map
의 키는 URL 경로이고, 값은 해당 핸들러 함수입니다.
5. 코드 구현
package main import ( "fmt" "net/http" "strings" ) // Router struct는 라우팅 규칙을 저장하는 데 사용됩니다. type Router struct { routes map[string]map[string]http.HandlerFunc } // NewRouter는 새 라우터 인스턴스를 만듭니다. func NewRouter() *Router { return &Router{ routes: make(map[string]map[string]http.HandlerFunc), } } // Handle 메서드는 경로를 등록하는 데 사용됩니다. func (r *Router) Handle(method, path string, handler http.HandlerFunc) { if _, ok := r.routes[method];!ok { r.routes[method] = make(map[string]http.HandlerFunc) } r.routes[method][path] = handler } // ServeHTTP 메서드는 HTTP 요청을 구문 분석하고 해당 핸들러 함수를 호출하는 데 사용됩니다. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { methodRoutes, ok := r.routes[req.Method] if!ok { http.NotFound(w, req) return } handler, ok := methodRoutes[req.URL.Path] if!ok { // 동적 라우팅 처리 for route, h := range methodRoutes { if params := matchDynamicRoute(route, req.URL.Path); params != nil { req.URL.Query().Set("params", strings.Join(params, ",")) h(w, req) return } } http.NotFound(w, req) return } handler(w, req) } // matchDynamicRoute 함수는 동적 경로를 일치시키는 데 사용됩니다. func matchDynamicRoute(route, path string) []string { routeParts := strings.Split(route, "/") pathParts := strings.Split(path, "/") if len(routeParts) != len(pathParts) { return nil } var params []string for i, part := range routeParts { if strings.HasPrefix(part, ":") { params = append(params, pathParts[i]) } else if part != pathParts[i] { return nil } } return params } func main() { router := NewRouter() // 정적 경로 등록 router.Handle("GET", "/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Hello, world!") }) // 동적 경로 등록 router.Handle("GET", "/hello/:name", func(w http.ResponseWriter, req *http.Request) { params := req.URL.Query().Get("params") name := strings.Split(params, ",")[0] fmt.Fprintf(w, "Hello, %s!", name) }) http.ListenAndServe(":8080", router) }
코드 설명
Router
struct: 라우팅 규칙을 저장하는 데 사용되며 중첩된map
을 사용하여 다른 HTTP 메서드 및 URL 경로에 해당하는 핸들러 함수를 저장합니다.NewRouter
함수: 새 라우터 인스턴스를 만드는 데 사용됩니다.Handle
메서드: 경로를 등록하고 HTTP 메서드, URL 경로 및 핸들러 함수를Router
struct에 저장하는 데 사용됩니다.ServeHTTP
메서드: HTTP 요청을 구문 분석하는 데 사용됩니다. 먼저 요청된 HTTP 메서드가 있는지 확인한 다음 URL 경로가 일치하는지 확인합니다. 일치하는 정적 경로가 없으면 동적 경로와 일치하려고 시도합니다.matchDynamicRoute
함수: 동적 경로를 일치시키고 URL 경로의 매개변수가 일치하는지 확인하는 데 사용됩니다.
6. 실행 및 테스트
코드를 main.go
로 저장하고 실행합니다.
go run main.go
그런 다음 다음을 방문합니다.
http://localhost:8080/
는"Hello, world!"
를 반환합니다.http://localhost:8080/hello/Go
는"Hello, Go!"
를 반환합니다.- 다른 경로를 방문하면
404 Not Found
가 반환됩니다.
7. 미들웨어 구현
미들웨어는 요청을 처리하기 전이나 후에 실행되는 함수입니다. 로깅, 인증, 오류 처리 등에 사용할 수 있습니다. 라우터에서 미들웨어를 구현하는 것은 매우 간단합니다. http.HandlerFunc
를 받고 새 http.HandlerFunc
를 반환하는 함수 유형을 정의하기만 하면 됩니다.
// Middleware는 미들웨어 함수 유형입니다. type Middleware func(http.HandlerFunc) http.HandlerFunc // Logger는 간단한 로깅 미들웨어입니다. func Logger(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { fmt.Printf("Received %s request for %s\n", req.Method, req.URL.Path) next(w, req) } } func main() { router := NewRouter() // 정적 경로를 등록하고 미들웨어를 적용합니다. router.Handle("GET", "/", Logger(func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Hello, world!") })) // 동적 경로를 등록하고 미들웨어를 적용합니다. router.Handle("GET", "/hello/:name", Logger(func(w http.ResponseWriter, req *http.Request) { params := req.URL.Query().Get("params") name := strings.Split(params, ",")[0] fmt.Fprintf(w, "Hello, %s!", name) })) http.ListenAndServe(":8080", router) }
코드 설명
Middleware
유형:http.HandlerFunc
를 받고 새http.HandlerFunc
를 반환하는 미들웨어 함수 유형을 정의합니다.Logger
함수: 요청을 처리하기 전에 요청 메서드와 URL 경로를 출력하는 간단한 로깅 미들웨어입니다.main
함수에서 각 경로의 핸들러 함수에Logger
미들웨어를 적용합니다.
8. 요약
이 튜토리얼에서는 Go 언어의 net/http
패키지를 사용하여 간단한 웹 라우터를 구현하는 방법을 보여줍니다. 동시에 HTTP 관련 지식, 일반적인 라우팅 구현 방법 및 이를 기반으로 미들웨어를 구현하는 방법을 소개합니다. 다음과 같이 이 기반을 확장할 수 있습니다.
PUT
,DELETE
등과 같은 더 많은 HTTP 메서드 지원- 인증, 속도 제한 등과 같은 더 복잡한 미들웨어 기능 추가
- 정규 표현식 라우팅과 같은 고급 매개변수 구문 분석 구현
이러한 방식으로 HTTP 서버의 기본 작동 원리를 익힐 수 있을 뿐만 아니라 자체 웹 프레임워크를 사용자 정의하고 Go 언어의 높은 성능과 유연성을 누릴 수 있습니다.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 Go 서비스를 배포하기 위한 최고의 플랫폼인 **Leapcell**을 추천합니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 손쉽게 개발하십시오.
🌍 무제한 프로젝트를 무료로 배포
사용한 만큼만 비용을 지불하십시오. 요청도 없고 요금도 없습니다.
⚡ 사용한 만큼 지불, 숨겨진 비용 없음
유휴 요금 없이 원활한 확장성만 제공됩니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ