WEB5 폴더에 작성했다.
지금까지 배운것을 이용해서 RESTful API를 구현해보자!
myapp 폴더를 생성하고 간단한 handler와 test코드를 작성한다.
app.go
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Get UserInfo by /users/{id}")
}
// NewHandler make a new myapp handler
func NewHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/users", usersHandler)
return mux
}
app_test.go
func TestIndex(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler()) // http서버를 모의한 테스트 서버가 나온다.
defer ts.Close()
resp, err := http.Get(ts.URL)
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
data, _ := ioutil.ReadAll(resp.Body)
assert.Equal("Hello World", string(data))
}
func TestUsers(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler())
defer ts.Close()
resp, err := http.Get(ts.URL + "/users")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
data, _ := ioutil.ReadAll(resp.Body)
assert.Contains(string(data), "Get UserInfo") // 어떤 문자열이 포함되어 있으면 OK
}
저번 포스트에 해본 것들이라 쉽게 작성할 수 있을 것이다.
이제 user ID를 Get 할 수 있게 test코드를 수정해보자(완전한 건 아니고 그냥 ID만 출력할 정도로)
User Id에 맞게 파싱하는 작업이 필요한데 우리는 Gorilla_mux 패키지를 이용할 것이다.
go get -u github.com/gorilla/mux
를 터미널에서 설치
http.NewServeMux()
대신에 Gorilla 패키지의 mux.NewRouter()
를 사용할 것이다.
app.go 수정
...
func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) // 알아서 ID를 파싱을 해줌
fmt.Fprint(w, "User Id:", vars["id"])
}
func NewHandler() http.Handler {
mux := mux.NewRouter()
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/users", usersHandler)
mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
return mux
}
app_test.go UserInfo 부분 추가
...
func TestGetUserInfo(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler())
defer ts.Close()
resp, err := http.Get(ts.URL + "/users/89")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
data, _ := ioutil.ReadAll(resp.Body)
assert.Contains(string(data), "User Id:89")
resp, err = http.Get(ts.URL + "/users/56")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
data, _ = ioutil.ReadAll(resp.Body)
assert.Contains(string(data), "User Id:56")
}
PASS가 나오는 것을 확인할 수 있다.
이제 Create(Method POST)로 User를 등록해보자!
GET과 POST처럼 어떤 메소드로 보내느냐에 따라서 다른 핸들러를 사용해야 한다.
app.go 수정
type User struct { // json을 사용하기 위한 user struct
ID int `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
...
func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
/*vars := mux.Vars(r) // 알아서 ID를 파싱을 해줌
fmt.Fprint(w, "User Id:", vars["id"]) // 그냥 User Id라고 정해준거여서 json을 사용할 때 오류남*/
// 그냥 임의로 정해줌
user := new(User)
user.ID = 2
user.FirstName = "tucker"
user.LastName = "kim"
user.Email = "soosungp33@gmail.com"
w.Header().Add("Content_Type", "application/json")
w.WriteHeader(http.StatusOK)
data, _ := json.Marshal(user)
fmt.Fprint(w, string(data))
}
func createUserHandler(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, err)
return
}
// Created User (실질적으로 user를 등록), (user를 기억하는 부분은 나중에)
user.ID = 2
user.CreatedAt = time.Now() // 현재 시간을 만들어진 시간으로 한다.
w.Header().Add("Content_Type", "application/json")
w.WriteHeader(http.StatusCreated) // 잘 만들었다고 알려줌
data, _ := json.Marshal(user) // user를 json 포멧에 맞게 변경(json 다루는 포스팅에서 다 한 내용)
fmt.Fprint(w, string(data))
}
...
func NewHandler() http.Handler {
mux := mux.NewRouter()
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/users", usersHandler).Methods("GET") // GET메소드일 때 이 핸들러가 불려라
mux.HandleFunc("/users", createUsersHandler).Methods("POST") // POST메소드일 때 이 핸들러가 불려라
mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
return mux
}
app_test.go 추가
func TestGetUserInfo(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler())
defer ts.Close()
resp, err := http.Get(ts.URL + "/users/89") // user id를 임의로 정해서 줌
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
data, _ := ioutil.ReadAll(resp.Body)
assert.Contains(string(data), "No User Id:89") // id를 보내주게 될 때 에러가 난다.
// 89라는 Id를 만든 적이 없기 때문
// 따라서 89라는 Id가 없다라고 변경해준다.(밑에 map을 이용해서 user정보를 저장해줄 때 보면 이해가 될 것이다.)
resp, err = http.Get(ts.URL + "/users/56")
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
data, _ = ioutil.ReadAll(resp.Body)
assert.Contains(string(data), "No User Id:56")
}
...
func CreateUserInfo(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler())
defer ts.Close()
// Get 대신 Post 사용 (url, response, body) -> json을 사용할 것이다.
resp, err := http.Post(ts.URL+"/users", "application/json",
strings.NewReader(`{"first_name":"tucker", "last_name":"kim", "email":"soosungp33@gmail.com"}`))
assert.NoError(err)
assert.Equal(http.StatusCreated, resp.StatusCode)
// 읽어서 재대로 왔는지 확인
user := new(User)
err = json.NewDecoder(resp.Body).Decode(user)
assert.NoError(err)
assert.NotEqual(0, user.ID) // user의 ID가 0이 아니여야함.
id := user.ID // TestGetUserInfo함수와 다르게 실제 user ID정보가 오도록 해보자
resp, err = http.Get(ts.URL + "/users/" + strconv.Itoa(id)) // int를 string으로 변경해서 id부분에 대입
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
user2 := new(User)
err = json.NewDecoder(resp.Body).Decode(user2)
assert.NoError(err)
assert.Equal(user.ID, user2.ID) // 서로 같아야 맞음
assert.Equal(user.FirstName, user2.FirstName)
}
이렇게 수정하고 실행시켜보면 오류가 난다.
그 이유는 Create된 유저의 정보를 등록하지 않아서 그런 것이다.(app.go의 getUserInfoHandler 함수에서 임의로 2라는 ID를 가져옴)
(해당 유저 정보가 있으면 정보를 리턴하고 없으면 No User ID를 리턴할 수 있게 변경해함)
유저 정보를 저장할 수 있게 map을 이용하자!
app.go 수정
...
var userMap map[int] * User // user정보를 저장할 Map 생성
var lastID int // ID는 매번 바뀌어야 하므로 변수 설정
...
func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"]) // string을 int로 변경
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, err)
return
}
user, ok := userMap[id] // map에 ID에 해당하는 것이 있는지
if !ok { // map에 해당하는 ID가 없으면
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "No User Id:", id)
return
}
// ID가 있으면 그 user의 정보를 보내준다.
w.Header().Add("Content_Type", "application/json")
w.WriteHeader(http.StatusOK)
data, _ := json.Marshal(user)
fmt.Fprint(w, string(data))
}
...
func createUserHandler(w http.ResponseWriter, r *http.Request) {
...
// Created User (실질적으로 user를 등록), (user를 기억하는 부분은 나중에)
lastID++
user.ID = lastID
user.CreatedAt = time.Now() // 현재 시간을 만들어진 시간으로 한다.
userMap[user.ID] = user // map에 user 정보를 저장
w.Header().Add("Content_Type", "application/json")
...
}
func NewHandler() http.Handler {
userMap = make(map[int] *User)
lastID = 0
...
}
이제 실제 ID가 map에 저장되서 그 map에 ID가 있는지 없는지 검사하는 방식으로 구현된다.
PASS되는 것을 볼 수 있다.
🔔 이번 포스팅에는 REST API의 GET과 POST(Create)를 다뤄보았고 다음은 GET할 때 등록된 유저들의 list를 보여주는 부분(실제로 사용해보기)과 DELETE, UPDATE하는 부분에 대해 공부하고 포스팅해봐야겠다.