[Go] Go Web Programming Basic 03 - Routing
Routing
이번에는 좀더 Routing 에 대해서 깊이 있게 알아보자.
HTTP Method
웹은 기본적으로 CRUD를 위한 GET, POST, PUT, DELETE 를 제공한다.
그 밖에도 HEAD, PATHCH 등 다양한 메소드를 제공한다.
이전 예제에서 핸들러를 단순 등록하면 모든 Method 를 받아 들일 수 있다.
그러나 이는 좋은 방법이 아니며, 용도에 맞는 메소드를 지정하여 endpoint 를 열어주는 것이 필요하다.
Method 이용하기.
main.go 에 다음과 같이 내용을 변경해보자.
package main
import (
"fmt"
"html"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HelloWorld).Methods("GET")
r.HandleFunc("/path/value", ResponsePath).Methods("GET")
r.HandleFunc("/users", CreateUser).Methods("POST")
r.HandleFunc("/users", UpdateUser).Methods("PUT")
r.HandleFunc("/users", DeleteUser).Methods("DELETE")
r.HandleFunc("/my", CallMy).Methods("POST", "PUT")
http.Handle("/", r)
fmt.Println("Server start on port: ", 8080)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World")
}
func ResponsePath(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Your request path is :%q", html.EscapeString(r.URL.Path))
}
func CreateUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "CreateUser :")
}
func UpdateUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "UpdateUser :")
}
func DeleteUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "DeleteUser :")
}
func CallMy(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Call My Page :")
}
/users
경로에 대해서 POST, PUT, DELETE 메소드를 각자 지정하고, 필요한 핸들러를 작성해 주었다.
위처럼 Methods() 함수를 이용하여 다양한 요청을 처리할 수 있다.
/my
경로에 대해서는 POST, PUT 2개의 메소드만 허용하도록도 설정해 보았다.
테스트 하기.
✗ curl http://localhost:8080/users -X POST
CreateUser :
✗ curl http://localhost:8080/users -X PUT
UpdateUser :
✗ curl http://localhost:8080/users -X DELETE
DeleteUser :
/users
에대서 3개의 메소드 호출이 정상적으로 라우팅 됨을 확인할 수 있다.
✗ curl http://localhost:8080/my -X PUT
Call My Page :
✗ curl http://localhost:8080/my -X POST
Call My Page :
✗ curl -i http://localhost:8080/my -X GET
HTTP/1.1 405 Method Not Allowed
Date: Thu, 10 Dec 2020 06:57:42 GMT
Content-Length: 0
/my
에 대해서는 PUT, POST 만 허용하고, GET 으로 호출했을때 헤더로 HTTP/1.1 405 Method Not Allowed
로 응답되었음을 확인할 수 있다.
이처럼 http method 별로 라우팅을 수행하여 적절한 엔드포인트를 제공할 수 있음을 알게 되었다.
Subrouting 으로 엔드포인트 구분하기.
보통 rest api 는 엔드포인트 prefix 를 구분하여 서비스를 제공한다.
예를 들어 /api 라는 prefix 를 붙여서 api 임을 알려주고, 특정 요구사항에 따라서 엔드포인트를 다양하게 분리할 수 있다.
예시:
- /api/users POST, PUT
- /api/users/{id} GET, DELETE
- /api/users/{id}/summary GET
- /api/subjects POST, PUT
- /api/subjects/{id} GET, DELETE
위와 같다고 하면 다음과 같이 서브 경로를 지정해 줄 수 있다.
main.go 파일을 아래와 같이 수정해보자.
package main
import (
"fmt"
"html"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HelloWorld).Methods("GET")
r.HandleFunc("/path/value", ResponsePath).Methods("GET")
// r.HandleFunc("/users", CreateUser).Methods("POST")
// r.HandleFunc("/users", UpdateUser).Methods("PUT")
// r.HandleFunc("/users", DeleteUser).Methods("DELETE")
// r.HandleFunc("/my", CallMy).Methods("POST", "PUT")
mainSubRouter := r.PathPrefix("/api").Subrouter()
userSubRouter := mainSubRouter.PathPrefix("/users").Subrouter()
userSubRouter.HandleFunc("", CreateUser).Methods("POST")
userSubRouter.HandleFunc("/", UpdateUser).Methods("PUT")
userSubRouter.PathPrefix("/{id}").HandlerFunc(SelectUser).Methods("GET")
userSubRouter.PathPrefix("/{id}").HandlerFunc(DeleteUser).Methods("DELETE")
subjectSubRouter := mainSubRouter.PathPrefix("/subjects").Subrouter()
subjectSubRouter.PathPrefix("/").HandlerFunc(CreateSubject).Methods("POST")
subjectSubRouter.PathPrefix("/").HandlerFunc(UpdateSubject).Methods("PUT")
subjectSubRouter.PathPrefix("/{id}").HandlerFunc(SelectSubject).Methods("GET")
subjectSubRouter.PathPrefix("/{id}").HandlerFunc(DeleteSubject).Methods("DELETE")
fmt.Println("Server start on port: ", 8080)
log.Fatal(http.ListenAndServe(":8080", r))
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World")
}
func ResponsePath(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Your request path is :%q", html.EscapeString(r.URL.Path))
}
func CreateUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "CreateUser :")
}
func SelectUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "SelectUser :")
}
func UpdateUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "UpdateUser :")
}
func DeleteUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "DeleteUser :")
}
func CallMy(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Call My Page :")
}
func CreateSubject(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "CreateSubject :")
}
func UpdateSubject(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "UpdateSubject :")
}
func SelectSubject(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "SelectSubject :")
}
func DeleteSubject(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "DeleteSubject :")
}
변경이 발생된 부분은 아래와 같다.
... 생략
mainSubRouter := r.PathPrefix("/api").Subrouter()
userSubRouter := mainSubRouter.PathPrefix("/users").Subrouter()
userSubRouter.HandleFunc("", CreateUser).Methods("POST")
userSubRouter.HandleFunc("/", UpdateUser).Methods("PUT")
... 생략
- r.PathPrefix(“/api”).Subrouter(): Subrouter 를 생성하고, prefix 하위에 필요한 라우팅을 묶어줄 수 있다.
- userSubRouter.HandleFunc(“”, CreateUser).Methods(“POST”): 부분은 prefix 를 ““로 두었고 핸들 함수를 정의하고 있다.
- userSubRouter.HandleFunc(“/”, UpdateUser).Methods(“PUT”): 부분은 위 내역과 동일하며, prefix 를 “/”로 두었다.
테스트 수행하기
✗ curl http://localhost:8080/api/users/1
SelectUser :
✗ curl http://localhost:8080/api/users/ -X POST
CreateUser :
✗ curl http://localhost:8080/api/users/ -X PUT
404 page not found
✗ curl http://localhost:8080/api/subjects/1
SelectSubject :
✗ curl http://localhost:8080/api/subjects/ -X POST
CreateSubject :
보는 바와 같이 curl http://localhost:8080/api/users/ -X PUT 로 처리되면 404 page not found 가 되었음을 알 수 있다.
Wrap Up
지금까지 라우팅에 대한 기본적인 사항에 대해서 알아 보았다.
추가적인 필터링과 호스트 필터링 등에 대한 내역은 Document 문서를 참고하면 좋을듯 하다.
관련 Git 에서 소스코드를 확인 가능