Go & Gorilla/Mux: 웹 앱을 빌드하는 데 필요한 모든 것
Takashi Yamamoto
Infrastructure Engineer · Leapcell

소개
gorilla/mux
는 gorilla Web
개발 툴킷의 라우팅 관리 라이브러리입니다. gorilla Web
개발 패키지는 Go
언어로 Web
서버 개발을 돕는 툴킷입니다. 폼 데이터 처리(gorilla/schema
), websocket
통신(gorilla/websocket
), 미들웨어(gorilla/handlers
), 세션
관리(gorilla/sessions
), 보안 쿠키
처리(gorilla/securecookie
) 등 다양한 측면을 다룹니다.
mux
는 다음과 같은 장점이 있습니다.
- 표준
http.Handler
인터페이스를 구현하며net/http
표준 라이브러리와 함께 사용할 수 있습니다. 매우 가볍습니다. - 요청의 호스트 이름, 경로, 경로 접두사, 프로토콜,
HTTP
헤더, 쿼리 문자열 및HTTP
메서드를 기반으로 핸들러를 매칭할 수 있습니다. 또한 사용자 정의 매칭 로직을 지원합니다. - 변수를 호스트 이름, 경로 및 요청 파라미터에 사용할 수 있으며 정규 표현식을 지정할 수 있습니다.
- 파라미터를 지정된 핸들러에 전달하여 완전한
URL
을 구성할 수 있습니다. - 경로 그룹화를 지원하여 관리 및 유지 관리에 편리합니다.
빠른 시작
이 기사의 코드는 Go Modules
를 사용합니다.
gorilla/mux
라이브러리 설치:
go get -u github.com/gorilla/gorilla/mux
다음으로 음악 정보를 관리하는 Web
서비스를 작성합니다. 각 음악 조각은 MusicID
로 고유하게 식별됩니다.
- 음악의 구조 정의:
type Music struct { MusicID string `json:"music_id"` Name string `json:"name"` Artists []string `json:"artists"` Album string `json:"album"` ReleasedAt string `json:"released_at"` } var ( mapMusics map[string]*Music slcMusics []*Music )
- 파일에서 데이터를 로드하는
init()
함수 정의:
func init() { mapMusics = make(map[string]*Music) slcMusics = make([]*Music, 0, 1) data, err := ioutil.ReadFile("../data/musics.json") if err != nil { log.Fatalf("failed to read musics.json:%v", err) } err = json.Unmarshal(data, &slcMusics) if err != nil { log.Fatalf("failed to unmarshal musics:%v", err) } for _, music := range slcMusics { mapMusics[music.MusicID] = music } }
- 전체 목록을 반환하는 핸들러 함수와 특정 음악 조각에 대한 핸들러 함수 두 개 정의:
func MusicsHandler(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.Encode(slcMusics) } func MusicHandler(w http.ResponseWriter, r *http.Request) { music, ok := mapMusics[mux.Vars(r)["music_id"]] if!ok { http.NotFound(w, r) return } enc := json.NewEncoder(w) enc.Encode(music) }
- 핸들러 등록:
func main() { r := mux.NewRouter() r.HandleFunc("/", MusicsHandler) r.HandleFunc("/musics/{music_id}", MusicHandler) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
mux
의 사용법은 net/http
와 매우 유사합니다. 먼저 mux.NewRouter()
를 호출하여 *mux.Router
형식의 라우팅 객체를 생성합니다. 이 라우팅 객체가 핸들러를 등록하는 방식은 표준 라이브러리의 *http.ServeMux
와 완전히 동일합니다. 즉, HandleFunc()
메서드를 호출하여 func(http.ResponseWriter, *http.Request)
형식의 핸들러 함수를 등록하고, Handle()
메서드를 호출하여 http.Handler
인터페이스를 구현하는 핸들러 객체를 등록합니다. 위에서는 음악 정보 목록을 표시하는 것과 특정 음악 조각의 정보를 표시하는 두 개의 핸들러 함수가 등록되어 있습니다.
경로 /musics/{music_id}
는 변수를 사용하고 있습니다. 변수 이름은 {}
안에 지정됩니다. 경로는 특정 부분을 매칭할 수 있습니다. 핸들러 함수에서 요청 r
의 라우팅 변수는 mux.Vars(r)
를 통해 얻을 수 있으며, 이는 map[string]string
을 반환하고 변수 이름을 사용하여 액세스할 수 있습니다(예: 위의 MusicHandler
에서 변수 music_id
에 대한 액세스).
*mux.Router
는 http.Handler
인터페이스도 구현하므로 http.Handle("/", r)
의 핸들러 객체 파라미터로 직접 등록할 수 있습니다. 여기서 루트 경로 /
가 등록되는데, 이는 모든 요청 처리를 *mux.Router
에게 위임하는 것과 같습니다.
마지막으로 http.ListenAndServe(":8080", nil)
은 여전히 Web
서버를 시작하고 들어오는 요청을 기다리는 데 사용됩니다.
실행 후 브라우저에 localhost:8080
을 입력하면 음악 목록이 표시됩니다. localhost:8080/musics/[특정 MusicID]
를 입력하면 해당 음악의 자세한 정보가 표시됩니다. 사용 프로세스에서 mux
라이브러리가 매우 가볍고 표준 라이브러리 net/http
와 잘 통합될 수 있음을 알 수 있습니다.
또한 정규 표현식을 사용하여 변수의 패턴을 제한할 수 있습니다. MusicID
에 고정된 패턴이 있다고 가정합니다(예: M001-001
, 즉 문자 M
으로 시작하고 그 뒤에 3자리 숫자가 오고 -
와 3자리 숫자로 연결됨. 이는 정규 표현식 M\d{3}-\d{3}
으로 나타낼 수 있음). 변수와 정규 표현식을 구분하려면 변수 이름 뒤에 :
를 추가합니다.
r.HandleFunc("/musics/{music_id:M\\d{3}-\\d{3}}", MusicHandler)
유연한 매칭 방법
mux
는 다양한 요청 매칭 방법을 제공합니다. 대조적으로 net/http
는 특정 경로만 지정할 수 있어 약간 서투릅니다.
- 경로의 도메인 이름 또는 서브도메인 이름 지정:
r.Host("musicplatform.com") r.Host("{subdomain:[a-zA-Z0-9]+}.musicplatform.com")
위의 경로는 도메인 이름 musicplatform.com
또는 해당 서브도메인의 요청만 허용합니다. 도메인 이름을 지정할 때 정규 표현식을 사용할 수 있습니다. 두 번째 줄의 코드는 서브도메인 이름의 첫 번째 부분이 여러 글자 또는 숫자여야 한다고 제한합니다.
- 경로 접두사 지정:
// 경로 접두사가 `/musics/`인 요청만 처리 r.PathPrefix("/musics/")
- 요청 방법 지정:
// GET/POST 요청만 처리 r.Methods("GET", "POST")
- 사용된 프로토콜(
HTTP
/HTTPS
):
// https 요청만 처리 r.Schemes("https")
- 헤더:
// 헤더 X-Requested-With의 값이 XMLHTTPRequest인 요청만 처리 r.Headers("X-Requested-With", "XMLHTTPRequest")
- 쿼리 파라미터(즉,
URL
에서?
뒤에 오는 부분):
// 쿼리 파라미터에 key=value가 포함된 요청만 처리 r.Queries("key", "value")
- 결합된 조건:
r.HandleFunc("/", HomeHandler) .Host("musicstore.com") .Methods("GET") .Schemes("http")
또한 mux
는 사용자 정의 매처도 허용합니다. 사용자 정의 매처는 타입 func(r *http.Request, rm *RouteMatch) bool
의 함수로, 매치가 성공했는지 여부를 요청 r
의 정보에 따라 결정합니다. http.Request
구조에는 HTTP
메서드, HTTP
버전 번호, URL
, 헤더 등 많은 정보가 포함되어 있습니다. 예를 들어 HTTP/1.1
요청만 처리해야 하는 경우 다음과 같이 작성할 수 있습니다.
r.MatchrFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 1 && r.ProtoMinor == 1 })
mux
는 경로 등록 순서대로 매칭합니다. 따라서 특별한 경로를 앞에 두고 일반적인 경로를 뒤에 두는 것이 좋습니다. 반대의 경우 특별한 경로는 매치되지 않습니다.
r.HandleFunc("/specific", specificHandler) r.PathPrefix("/").Handler(catchAllHandler)
서브 경로
경로를 그룹화하고 관리하면 프로그램 모듈이 더 명확해지고 유지 관리가 더 쉬워질 수 있습니다. 웹사이트가 비즈니스를 확장하고 다양한 유형의 음악(예: 팝, 록 등)과 관련된 정보를 추가한다고 가정합니다. 별도의 관리를 위해 여러 서브 경로를 정의할 수 있습니다.
r := mux.NewRouter() ps := r.PathPrefix("/pop_musics").Subrouter() ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) rs := r.PathPrefix("/rock_musics").Subrouter() rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler)
서브 경로는 일반적으로 경로 접두사로 제한됩니다. r.PathPrefix()
는 *mux.Route
객체를 반환합니다. 해당 Subrouter()
메서드를 호출하면 서브 경로 객체 *mux.Router
가 생성된 다음 이 객체의 HandleFunc/Handle
메서드를 통해 핸들러 함수를 등록합니다.
서브 경로 방식을 사용하면 각 부분의 경로를 해당 모듈에 분산시켜 로드할 수도 있습니다. pop_music.go
파일에 InitPopMusicsRouter()
메서드를 정의하여 팝 음악과 관련된 경로를 등록하는 역할을 합니다.
func InitPopMusicsRouter(r *mux.Router) { ps := r.PathPrefix("/pop_musics").Subrouter() ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) }
rock_music.go
파일에 InitRockMusicsRouter()
메서드를 정의하여 록 음악과 관련된 경로를 등록하는 역할을 합니다.
func InitRockMusicsRouter(r *mux.Router) { rs := r.PathPrefix("/rock_musics").Subrouter() rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler) }
main.go
의 메인 함수에서:
func main() { r := mux.NewRouter() InitPopMusicsRouter(r) InitRockMusicsRouter(r) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
서브 경로 매칭은 경로 접두사를 포함해야 합니다. 즉, /pop_musics/
는 PopMusicsHandler
와 매치될 수 있습니다.
경로 URL
구성
경로에 이름을 지정할 수 있습니다. 예를 들어:
r.HandleFunc("/musics/{music_id}", MusicHandler).Name("music")
위의 경로에는 파라미터가 있습니다. 파라미터 값을 전달하여 완전한 경로를 구성할 수 있습니다.
fmt.Println(r.Get("music").URL("music_id", "M001-001")) // /musics/M001-001 <nil>
반환되는 것은 *url.URL
객체이며 해당 경로 부분은 /musics/M001-001
입니다. 이는 호스트 이름과 쿼리 파라미터에도 적용됩니다.
r := mux.Router() r.Host("{name}.musicplatform.com"). Path("/musics/{music_id}"). HandlerFunc(MusicHandler). Name("music") url, err := r.Get("music").URL("name", "user1", "music_id", "M001-001")
경로의 모든 파라미터를 지정해야 하며 값은 지정된 정규 표현식을 충족해야 합니다(있는 경우). 실행 출력은 다음과 같습니다.
$ go run main.go http://user1.musicplatform.com/musics/M001-001
URLHost()
를 호출하여 호스트 이름 부분만 생성하고 URLPath()
를 호출하여 경로 부분만 생성할 수 있습니다.
미들웨어
mux
는 미들웨어 유형 MiddlewareFunc
를 정의합니다.
type MiddlewareFunc func(http.Handler) http.Handler
이 유형을 충족하는 모든 함수는 mux
에 대한 미들웨어로 사용할 수 있습니다. 미들웨어는 라우팅 객체 *mux.Router
의 Use()
메서드를 호출하여 적용됩니다. 미들웨어를 작성할 때 원래 핸들러가 일반적으로 전달됩니다. 미들웨어에서 원래 핸들러 함수는 수동으로 호출되고 일반적인 처리 로직이 전후에 추가됩니다.
func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.RequestURI) next.ServeHTTP(w, r) }) }
웹사이트에서 음악 관련 페이지에 액세스하려면 로그인이 필요하다고 가정합니다. 이 로직을 처리하는 미들웨어를 작성할 수 있습니다. Cookie
가 존재하지 않거나 유효하지 않으면 로그인 페이지로 리디렉션됩니다. 로그인에 성공하면 로그인이 성공했음을 나타내는 키가 token
인 Cookie
가 생성됩니다.
func login(w http.ResponseWriter, r *http.Request) { // 여기서는 html/template을 사용하여 템플릿을 구문 분석하고 로그인 페이지를 표시한다고 가정합니다. loginTemplate.ExecuteTemplate(w, "login.tpl", nil) } func doLogin(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.Form.Get("username") password := r.Form.Get("password") if username != "user" || password != "123456" { http.Redirect(w, r, "/login", http.StatusFound) return } token := fmt.Sprintf("username=%s&password=%s", username, password) data := base64.StdEncoding.EncodeToString([]byte(token)) http.SetCookie(w, &http.Cookie{ Name: "token", Value: data, Path: "/", HttpOnly: true, Expires: time.Now().Add(24 * time.Hour), }) http.Redirect(w, r, "/", http.StatusFound) }
로그인 페이지를 표시하기 위해 여러 template
템플릿 파일이 생성되고 html/template
을 사용하여 구문 분석됩니다.
- 로그인 표시 페이지:
<!-- login.tpl --> <form action="/login" method="post"> <label>Username:</label> <input name="username"><br> <label>Password:</label> <input name="password" type="password"><br> <button type="submit">Login</button> </form>
- 메인 페이지:
<ul> <li><a href="/pop_musics/">Pop Music</a></li> <li><a href="/rock_musics/">Rock Music</a></li> </ul>
- 음악 목록 및 세부 정보 페이지(예시):
<!-- pop_musics.tpl --> <ol> {{ range . }} <li> <p>Song Name: <a href="/pop_musics/{{ .MusicID }}">{{ .Name }}</a></p> <p>Release Date: {{ .ReleasedAt }}</p> <p>Artists: {{ range .Artists }}{{ . }}{{ if not $.Last }}, {{ end }}{{ end }}</p> <p>Album: {{ .Album }}</p> </li> {{ end }} </ol>
<!-- pop_music.tpl --> <p>MusicID: {{ .MusicID }}</p> <p>Song Name: {{ .Name }}</p> <p>Release Date: {{ .ReleasedAt }}</p> <p>Artists: {{ range .Artists }}{{ . }}{{ if not $.Last }}, {{ end }}{{ end }}</p> <p>Album: {{ .Album }}</p>
다음으로 템플릿을 구문 분석합니다.
var ( loginTemplate *template.Template ) func init() { var err error loginTemplate, err = template.New("").ParseGlob("./tpls/*.tpl") if err != nil { log.Fatalf("load templates failed:%v", err) } }
해당 페이지에 액세스하기 위한 로직:
func PopMusicsHandler(w http.ResponseWriter, r *http.Request) { loginTemplate.ExecuteTemplate(w, "pop_musics.tpl", slcMusics) } func PopMusicHandler(w http.ResponseWriter, r *http.Request) { music, ok := mapMusics[mux.Vars(r)["music_id"]] if!ok { http.NotFound(w, r) return } loginTemplate.ExecuteTemplate(w, "pop_music.tpl", music) }
해당 템플릿을 실행하고 음악 목록 또는 특정 음악 조각의 정보를 전달합니다. 이제 로그인한 사용자만 음악 관련 페이지에 액세스할 수 있도록 제한하고 로그인하지 않은 사용자가 액세스할 때 로그인 페이지로 리디렉션하는 미들웨어를 작성합니다.
func authenticateMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("token") if err!= nil { // 쿠키 없음 http.Redirect(w, r, "/login", http.StatusFound) return } data, _ := base64.StdEncoding.DecodeString(cookie.Value) values, _ := url.ParseQuery(string(data)) if values.Get("username")!= "user" || values.Get("password")!= "123456" { // 실패 http.Redirect(w, r, "/login", http.StatusFound) return } next.ServeHTTP(w, r) }) }
그런 다음 팝 음악 및 록 음악의 서브 경로에 미들웨어 authenticateMiddleware
(로그인 인증 필요)를 적용하고, 로그인 서브 경로는 필요하지 않습니다.
func InitPopMusicsRouter(r *mux.Router) { ps := r.PathPrefix("/pop_musics").Subrouter() // 여기 ps.Use(authenticateMiddleware) ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) } func InitRockMusicsRouter(r *mux.Router) { rs := r.PathPrefix("/rock_musics").Subrouter() // 여기 rs.Use(authenticateMiddleware) rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler) } func InitLoginRouter(r *mux.Router) { ls := r.PathPrefix("/login").Subrouter() ls.Methods("GET").HandlerFunc(login) ls.Methods("POST").HandlerFunc(doLogin) }
프로그램을 실행합니다(다중 파일 프로그램을 실행하는 방법에 유의).
$ go run.
localhost:8080/pop_musics/
에 액세스하면 localhost:8080/login
으로 리디렉션됩니다. 사용자 이름 user
와 비밀번호 123456
을 입력하고 로그인에 성공하면 메인 페이지가 표시됩니다. Cookie
가 유효한 한 후속 요청은 다시 확인할 필요가 없습니다.
결론
이 기사에서는 가볍고 강력한 라우팅 라이브러리 gorilla/mux
를 소개합니다. 다양한 요청 매칭 방법을 지원하며 서브 경로는 경로 관리를 크게 용이하게 합니다. 표준 라이브러리 net/http
와 호환되므로 net/http
를 사용하는 프로그램에 원활하게 통합하고 net/http
에 작성된 미들웨어 리소스를 활용할 수 있습니다. 다음 기사에서는 일반적으로 사용되는 미들웨어인 gorilla/handlers
를 소개합니다.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 Go 서비스를 배포하는 데 가장 적합한 플랫폼을 추천합니다. Leapcell
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 손쉽게 개발하세요.
🌍 무제한 프로젝트를 무료로 배포하세요
사용한 만큼만 지불하세요. 요청도 없고 요금도 없습니다.
⚡ 사용량에 따라 지불하고 숨겨진 비용은 없습니다.
유휴 요금 없이 원활한 확장성만 제공합니다.
🔹 트위터에서 팔로우하세요: @LeapcellHQ