golang
에서 http method
를 사용하는 방법은 매우 특이하고 재밌는데 routing
단계가 아니라 handler
내부에서 구분하여 로직을 처리한다.
func main() {
fmt.Println("start server!")
mux := http.NewServeMux()
mux.HandleFunc("/go", goHandler)
err := http.ListenAndServe(":8888", mux)
if err != nil {
fmt.Println(err)
}
}
다음의 코드를 보면, /go
url만 열려있고 goHandler
로 연결된다. 만약, /go
url을 요청할 때 http method
에 따라 결과를 다르게 반환하고, 입력을 달리주고 싶다면 어떻게 해야할까? 즉, RESTful 제약을 지킨 api를 만들기 위해 http method
를 사용하려면 어떻게 해야할까??
대부분의 언어들은 mux.HandleFunc("/go", goHandler)
와 같은 routing 단계에서 http method
를 명시해주어 구분하는데 golang
는 그렇지 않고, handler인 goHandler
내부에서 구분해야한다.
func goHandler(res http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET":
io.WriteString(res, "GET\n")
case "POST":
io.WriteString(res, "POST\n")
case "PUT":
io.WriteString(res, "PUT\n")
case "DELETE":
io.WriteString(res, "DELETE\n")
default:
io.WriteString(res, "FAILURE\n")
}
}
http.Request
내부에 Method
로 http method
를 알아낼 수 있다. switch-case
를 사용하여 어떤 http method
인지, 확인할 수 있고 지원하는 http method
라면 결과를 반환해주면된다. 한가지 알아두어야 할 것은 golang
의 switch-case
는 break
가 없어도 하나의 case
에 들어가면 switch
문에서 빠져나온다.
curl
로 http method
를 바꿔가며 테스트해보도록 하자
curl -X GET "http://localhost:8888/go"
GET
curl -X POST "http://localhost:8888/go"
POST
curl -X PUT "http://localhost:8888/go"
PUT
curl -X DELETE "http://localhost:8888/go"
DELETE
curl -X trace "http://localhost:8888/go"
FAILURE
잘 동작하는 것을 확인할 수 있다.
REST API와 같은 HTTP API를 만들 때, 클라이언트들은 URL length limit 이상의 데이터를 전송하고 싶을 때가 많이 있을 것이다. query parameter로는 도저히 넘기기에는 너무 많은 데이터들이 있는데 대부분 json
이나 form
과 같은 형식을 한다. 가령, 유저의 인적정보를 넘긴다고 할 때, 이름, 주소, 나이, 직장, 경력, 취미 등등의 정보는 query parameter로 넘기기 어렵다. 그렇기 때문에 request's body
에 정보를 포함하여 전송하는 방법을 많이 사용한다. 이때 사용되는 http method
가 대부분 POST, PUT이다.
위에서 보았듯이 go
에서는 http.HandleFunc
안의 *http.Request
를 사용하여, 들어오는 request에 대한 정보에 접근할 수 있다. *http.Request
에는 Body
필드가 있는데, 이를 통해서 request body에 접근할 수 있다.
func goHandler(res http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET":
io.WriteString(res, "GET\n")
case "POST":
body, err := ioutil.ReadAll(req.Body)
if err != nil {
fmt.Printf("Could not read body: %s\n", err)
}
fmt.Printf("%s: got request", body)
io.WriteString(res, "POST\n")
case "PUT":
io.WriteString(res, "PUT\n")
case "DELETE":
io.WriteString(res, "DELETE\n")
default:
io.WriteString(res, "FAILURE\n")
}
}
req.Body
은 io.ReadCloser
라는 인터페이스를 가지고 있는데, 내부적으로 Reader, Closer
를 가지고 있어 io.Reader
에 호환이 가능하다. 따라서 ioutil.ReadAll()
파라미터에 req.body
를 넣어 데이터를 바이트로 가져올 수 있다.
body, err := ioutil.ReadAll(req.Body)
이제 해당 데이터를 읽어오기만하면 된다.
한번 req.body
에 데이터를 전송해보자.
curl -X POST -d "hello world" "http://localhost:8888/go"
fmt
로 서버에 찍은 결과가 나온 것을 확인해보자.
hello world: got request
잘 찍힌 것을 확인할 수 있다.
이번에는 json
데이터를 전송해보자.
curl -X POST -d '{"name":"John", "age":30, "car":null}' "http://localhost:8888/go"
서버에 찍힌 로그를 확인하면
{"name":"John", "age":30, "car":null}: got request
정상적으로 전달된 것을 확인할 수 있다.
website에서 form
태그로 만들어진 데이터를 받은다고 하자. 비록 form
태그로 데이터를 받는 방식이 이제는 더 이상 메이저한 방법이 아니지만 아직도 많이 사용되고 예전에 만들어진 website에서의 호환을 위해서 구현해야한다.
이 역시도 *http.Request
값을 통해서 접근할 수 있고, query parameter, request body와 비슷한 방식으로 접근할 수 있다.
req.PostFormValue()
을 통해 접근할 수 있다. form
의 특성상 name=value
형식이기 때문에 req.PostFormValue()
에 name
부분을 넣으면 value
를 반환해준다.
func goHandler(res http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET":
io.WriteString(res, "GET\n")
case "POST":
myName := req.PostFormValue("name")
if myName == "" {
myName = "HTTP"
}
io.WriteString(res, fmt.Sprintf("hello %s\n", myName))
case "PUT":
io.WriteString(res, "PUT\n")
case "DELETE":
io.WriteString(res, "DELETE\n")
default:
io.WriteString(res, "FAILURE\n")
}
}
myName := req.PostFormValue("name")
다음과 같이 "name"
이라는 form data
의 name
부분을 넣어주면 value
를 반환해준다. 이를 http request를 보내어 확인해보도록 하자.
curl
에 -F
옵션을 넣으면 form data
를 전송할 수 있다.
curl -X POST -F 'name=Sammy' 'http://localhost:8888/go'
결과로 form 데이터의 value인 Sammy
를 반환해준다.
hello Sammy