Web Handler에 이어서 main함수의
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Bar!")
})
부분 중 fooHandler처럼 func(w http.ResponseWriter, r *http.Request) 부분을 바깥으로 빼서 수정 해줍니다.
func barHandeler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Bar!")
})
이렇게 수정한 뒤 main함수에 이에 해당하는 코드를 수정해줍니다.
http.HandleFunc("/bar", barHandler)
이렇게 각각 경로에 해당하는 Handler들을 등록했습니다. 이렇게 경로에 따라 분배해주는 라우터가 있는데 "Mux"라는 것입니다.
그래서 main함수에 라우터 클래스를 만들어서 등록하는 방식으로 수정 해봅시다.
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
mux.HandleFunc("/bar", barHandler)
mux.Handle("/foo", &fooHandler{})
http.ListenAndServe(":3000", mux)
}
이 상태에서 실행을 해도 전과 똑같은 화면이 출력됩니다.
이 전과 차이가 있다면 기존에는 HTTP에 정적으로 등록했는데 지금은 mux라는 인스턴스를 만들어서 거기에 등록해서
그 인스턴스를 넘겨주는 방식으로 바꾸었습니다.
우리가 서버에 요청할 때 request를 날리는데 이 request안에 필요한 argument의 input값을 넣을 수 있는데
func barHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name") // 1
if name == "" { // 2
name = "World"
}
fmt.Fprintf(w, "Hello %s!", name) // 3
}
1 : r.URL.Query()는 URL에서 정보를 뽑아내기 위해 사용하는 것이고, 그 결과에서 name이라는 argument를 Get하기 위해 r.URL.Query().Get("name")으로 작성했습니다.
즉, URL에서 name이라는 argument를 뽑아내서 그 값을 name에 넣어줍니다.
2 : 그 후 name값이 없으면 name변수에 World가 들어가도록 해줍니다.
3 : Fprintf를 사용하여 출력 시켜줍니다.
이 상태에서 실행 시켜 봅시다.
먼저 /bar경로로 들어가면 "Hello world!"가 출력되는 것을 볼 수 있는데
이 상태에서 ?name=bar를 경로에 추가 시켜주게 되면
"Hello bar!"가 출력 되는 것을 알 수 있습니다.
즉, name=임의적인name값 을 넣으면 Hello 임의적인name 값이 출력됨을 알 수 있습니다.
이제부터 JSON을 다루기 위해 User라는 struct를 만들어 봅시다.
type User struct {
FirstName string
LastName string
Email string
CreatedAt time.Time
}
그리고 fooHandler에서 Request값이 JSON으로 오기 때문에 그 것을 읽을 수 있도록 fooHandler부분을 수정해줍니다.
func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user := new(User) // 1
err := json.NewDecoder(r.Body).Decode(user) // 2
if err != nil { // 3
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "Bad Request: ", err)
return
}
user.CreatedAt = time.Now() // 4
data, _ := json.Marshal(user) // 5
w.WriteHeader(http.StatusOK) // 6
fmt.Fprint(w, string(data))
}
1 : User struct형태의 JSON이 올 것이기 때문에 그 값을 채워줄 인스턴스를 만들어 줍니다.
2 : 이 값을 JSON형태로 파싱을 해주어야 하는데 그것을 위해 NewDecoder(r.Body)를 사용하고, JSON형태의 데이터가 Body에 들어있기 때문에 NewDecoder의 인자로 reader인 r을 받기 때문에 r.Body를 넣어줍니다.
r.Body에 커서를 두게 되면
r.Body가 io.Reader를 포함하고 있음을 알 수 있고, NewDecoder는 io.Reader를 인자로 받고 있다는 걸 알 수 있습니다.
그리고 Body에서 값을 읽어서 user struct형태로 값을 채워주기 위해 Decode(user)를 사용합니다.
이 부분은 뭔가 데이터가 잘못될 수 있기 때문에 err변수에 넣어줍니다.
3 : 그래서 if문으로 err가 nil이 아닌경우에는 에러가 생겼기 때문에 w.WriteHeader(http.StatusBadRequest)로 잘못 보냈다라는것을 알려주고, fmt.Fprint(w, "Bad Request: ", err)로 Body에 쓸 수 있도록 작성한 후 더 이상 에러가 나지 않도록 return 시켜줍니다.
4 : 성공적으로 Decode가 됐을 때 err값이 nil이 되는데 그 때 user의 CreatedAt값을 현재로 바꾸어 주고,
5 : JSON을 다시 보내주려면 JSON data로 바꾸어 주어야 합니다. 지금 상태는 Go struct형태이기 때문인데 어떤 인터페이스를 받아서 JSON형태로 Encodding을 해주는 Marshal(user)를 사용해줍니다.
이 형태는 첫 번째 리턴 값으로 byte array가 나오고 두 번째 return값으로 error가 나오는데 error를 무시하기 위해 작성 해주었습니다.
6 : 잘 되었다고 알려주기 위해 사용하는데 string(data)으로 사용한 이유는 data가 byte[]형식이기 때문에 string으로 형변환 시켜준 것입니다.
이 상태에서 실행을 하게 되면
EndOfFile이라는 것이 뜬다. Body에 data를 넣어야 하기 때문에 URL에 데이터를 넣어도 결과는 똑같습니다.
이럴 때 필요한 것이 클라이언트 앱입니다. 크롬 앱 중에 Advanced REST client 라는 앱을 설치 해봅시다.
앱을 키고 실행해줍시다.
POST로 바꾸어주고, 주소는 http://localhost:3000/foo
로 지정해준 뒤, Body 부분에 user struct부분에 맞추어 JSON형태의 값을 넣어준 뒤 데이터를 날려줍니다.
결과를 보면
{"FirstName":"","LastName":"","Email":"changbeom@naver.com","CreatedAt":"2020-09-16T13:49:06.990073+09:00"}
Email과 시간을 제외한 모든 값들이 제대로 들어가지 않았습니다.
그 이유는 JSON에 입력했던 형식과 User struct에서 쓰는 형식이 다르기 때문에 서로 다르게 인식하는 것인데,
그것을 맞춰주어야 합니다.
type User struct {
FirstName string
LastName string
Email string
CreatedAt time.Time
}
여기에서 FirstName을 first_name으로 바꾸어 주어도 되지만 Go에서는 밑줄을 싫어합니다.
그렇기 때문에 이런식으로 옆에 Annotation(설명)을 붙여줍니다.
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
이렇게 하면 Decode하고 Marshal할 때 해당 컬럼 이름에 맞추어서 해줍니다.
다시 실행해봅시다!
제대로 들어간 것을 확인할 수 있습니다.
하지만 우리가 입력했던 형식이랑은 다르게 출력이 되었는데 그 이유는 JSON이 아닌 TEXT로 인식하고 있기 때문입니다.
그래서 func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
부분에 w.Header().Add("content-type", "application/json")
을 추가해준 뒤 다시 실행 시키면
정상적으로 동작하는 것을 확인 할 수 있습니다.
풀 소스
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
type fooHandler struct{}
func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user := new(User)
err := json.NewDecoder(r.Body).Decode(user)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "Bad Request: ", err)
return
}
user.CreatedAt = time.Now()
data, _ := json.Marshal(user)
w.Header().Add("content-type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(data))
}
func barHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
fmt.Fprintf(w, "Hello %s!", name)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
mux.HandleFunc("/bar", barHandler)
mux.Handle("/foo", &fooHandler{})
http.ListenAndServe(":3000", mux)
}
출처 : Turker의 Golang
'프로그래밍(Web) > Golang' 카테고리의 다른 글
[바미] Go - RESTful API(POST) (0) | 2020.12.17 |
---|---|
[바미] Go - RESTful API(GET) (0) | 2020.12.16 |
[바미] Go - File Upload 만들기. (0) | 2020.12.16 |
[바미] Go - test환경 만들기 (0) | 2020.12.16 |
[바미] Go - 간단한 Web Handler 만들기 (0) | 2020.12.16 |