GO Lang
GO_웹 제작(4)
u0jin
2020. 6. 23. 06:19
목표 : RESTful 코드 작성 ( GET / POST ) & 테스트 코드 실행
먼저 REST를 아주 간단히 파악하자면,
자원의 이름으로 해당 자원의 상태를 주고 받는 모든것을 말합니다.
다시말하면,
HTTP를 통해 자원을 명시하고, HTTP Method( GET ,POST , PUT , DELETE )를 통해 자원의 CRUD Operation을 적용하는것을 의미합니다.
- CRUD Operation
- Create : 생성 ( POST )
- Read : 조회 ( GET )
- Update : 수정 ( PUT )
- Delete : 삭제 ( DELETE )
- HEAD : header 정보 조회 ( HEAD )
REST라는 통일된 규격을 쓰면, 프레임워크 만들기에도 좋고, 상태를 주고 받을때도 편리하기 때문에 많이 사용합니다.
이정도 까지만 알고 코드로 넘어가 보겠습니다.
사용된 코드 : main.go / app.go / app_test.go
< main.go >
package main
import (
"net/http"
"yujinGo/web5/myapp"
)
func main() {
http.ListenAndServe(":3000", myapp.NewHandler())
}
< app.go >
package myapp
import (
"net/http"
"github.com/gorilla/mux"
)
// NewHandler make a new myapp handler
func NewHandler() http.Handler {
mux := mux.NewRouter()
return mux
}
기본 웹서버 틀이라고 보면 된다.
라우팅 설정에서 go의 외부 라이브러리인 mux 를 사용해야 하므로 설치를 진행해야한다.
깃헙 주소 : https://github.com/gorilla/mux
(* cmd에 go get github.com/gorilla/mux 를 입력하면 된다. )
package myapp
/* 핸들러를 만들어주는 패키지 */
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
)
/* User struct */
type 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"`
}
var userMap map[int]*User
/* 유저 정보를 들고있는 맵 */
var lastID int
/* 마지막 아이디를 등록할 변수 하나 만들어야함 -> 아이디는 매번 바뀌니까 */
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}")
}
func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
/*
클라이언트가 요청했는지 확인후,요청이 있는경우 해당 유저 정보 반환하도록 만들어야함
아이디정보를 뽑아옴
r.URL.Path 에서 id 만 뽑아오면 되는데 복잡하니까 이미있는 패키지를 쓸거임
*/
vars := mux.Vars(r)
/*
mux패키지 안에 vars 라는게 있는데 여기에 req를 넣으면 자동으로 id를 파싱해줌
유저가 보낸 인풋 정보가 들어잇음
*/
id, err := strconv.Atoi(vars["id"])
/*
아이디 정보를 뽑아오라는 뜻
vars 는 string 으로 되어있기 때문에 int 로 변환해줘야함
*/
if err != nil {
/* 에러가 있으면 */
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, err)
return
}
user, ok := userMap[id]
/*
id에 해당하는 애가 맵에 있는지 확인 하는 작업
*/
if !ok {
/* id 에 해당하는 애가 맵에 없다면 */
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "No User Id:", id)
return
}
/* 여기까지 온거면 유저가 있다는 말과 같음 */
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
data, _ := json.Marshal(user)
fmt.Fprint(w, string(data))
/*
fmt.Fprint(w, "User Id:89") 계속 이런식으로 쓸수 없기때문에 데이터를 가져오는거임 (id는 계속 바뀜)
fmt.Fprint(w, "User Id:", vars["id"]) 이렇게 둬도 에러가 뜸
->create 하고 비교할때, U라는 글자로 고정되어있기 때문에 에러가 나옴
*/
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
/*
클라이언트가 유저정보를 json으로 보냄
json을 읽어들여야함
json이 읽을수 있는 유저 struct를 만들어야함
유저생성하는 코드를 만들어야함
*/
user := new(User)
/*
유저 struct instance를 만들기
*/
err := json.NewDecoder(r.Body).Decode(user)
//json에서 뉴이토더 해서 req바디 읽어와 - 클라이언트에서 보낸거
/*
json에서 NewDecoder를 통해 req 바디를 읽어옴 = 클라이언트에서 보낸걸 읽어옴
*/
if err != nil {
/* 에러가 있다면 */
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, err)
return
}
/* 여기로 넘어오면 에러가 없다는 말과 같음 */
/* Created User */
lastID++
/*
유저 아이디는 매번 바껴야함 -> 유저 하나 만들때마다 하나씩 등록되어야함
*/
user.ID = lastID
user.CreatedAt = time.Now()
userMap[user.ID] = user
/*
유저맵에 유저 아이디에 해당하는 애를 등록함
*/
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
data, _ := json.Marshal(user)
fmt.Fprint(w, string(data))
}
/* NewHandler make a new myapp handler */
func NewHandler() http.Handler {
/* http.handler 인터페이스틑 리턴함 */
userMap = make(map[int]*User)
lastID = 0
/*
사용하기전에 초기화해줘야함 -> create 할때 유저정보 등록할꺼임
*/
mux := http.NewServeMux()
/*
mux를 깔았으니까 http.serverMux 아니고 이제 고릴라 먹스 가지고 쓸거양
*/
mux := mux.NewRouter()
/*
mux를 깔았기 때문에
mux := http.NewServeMux() 안쓰고
고릴라 먹스를 이용할거임
*/
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/users", usersHandler).Methods("GET")
/*
고릴라 먹스가 필요한 이유는 이거때문임.
뒤에 메서드라고 붙여서 우리가 어떤 메서드를 쓸때 이 핸들러를 써라 라고 알려줌
메서드를 지정해주지 않으면 그냥 상위 핸들러를 가져다가 써서 에러가 생김
*/
mux.HandleFunc("/users", createUserHandler).Methods("POST")
mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
/*
mux.HandleFunc("/users/89", getUserInfoHandler)
이렇게 코딩을 하면 아이디를 계속 추가해야하기때문에
고릴라 먹스 이용함 -> 먹스에 가면 예제가 나와있는데 자동으로 파싱이 된다.
*/
return mux
}
< app_test.go >
package myapp
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"strings"
"github.com/stretchr/testify/assert"
)
func TestIndex(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler())
/*
서버를 만듬 -> 핸들러 등록을 해줘야함
실제 서버가 아닌 모의테스트서버임
*/
defer ts.Close()
/* 항상 닫아줘야함 */
resp, err := http.Get(ts.URL)
/*
테스트서버의 url을 요청하면, 응답과 err가 반환됨
*/
assert.NoError(err)
/*
에러가 없다면
*/
assert.Equal(http.StatusOK, resp.StatusCode)
/*
인덱스 핸들러가 app.go에 정의되어있다면 에러가나지않는다.
*/
data, _ := ioutil.ReadAll(resp.Body)
/*
결과가 제대로 나오는지 확인
바디를 모두 읽어오게만듬
*/
assert.Equal("Hello World", string(data))
}
/*
TestIndex 와 똑같은데 인덱스 경로만 달라짐
*/
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")
/*
데이터에서 어떤 문자열이 포함되어있어야한다는 뜻
"Get UserInfo" 라는 문자열이 포함되어있어야함
TestIndex 와 TestUsers 가 각각 다른 핸들러가 불려지는걸 확인해야함
*/
}
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")
/*
아이디는 계속 바뀌기 때문에 GetUserInfo를 어떻게 할지 생각해야함
*/
}
/* 이제 create 하는 부분임 */
func TestCreateUser(t *testing.T) {
assert := assert.New(t)
ts := httptest.NewServer(NewHandler())
defer ts.Close()
/*
테스트 서버 만들었음
*/
/*
이제 http로 request 를 보내면 된다.
*/
resp, err := http.Post(ts.URL+"/users", "application/json",
strings.NewReader(`{"first_name":"yujin", "last_name":"kim", "email":"youjin@studentpartner.com"}`))
/*
get이 아닌 post로 보내보는 작업
post는 인자가 3개임 -> url string , contentType string , body io.Reader 이렇게 3개를 인자로 받아
응답은 resp http.Response , err error 가 나옴
테스트 서버의 url , json으로 보냄, 바디는 json할때 보낸거 처럼 strings에 뉴 리더를 만들어서 할거임
string 을 바로 보냄
*/
assert.NoError(err)
//우리는 create 에 한거니까 이러케해야해
assert.Equal(http.StatusCreated, resp.StatusCode)
/*
create 한거기때문에 ok를 쓰면 안됌
만약 에러가 뜬가면, 핸들러를 제대로 못불러서 그런거임
어떤 메소드를 부르느냐에 따라 다른 핸들러를 써야하는데
post 를 썻는데 get을 불러서 201 에러가 나오는거임
*/
/* 이제 유저정보 읽어오자 */
user := new(User)
err = json.NewDecoder(resp.Body).Decode(user)
assert.NoError(err)
assert.NotEqual(0, user.ID)
/* 유저 아이디가 0이면 안됌 */
id := user.ID
/* 이 아이디를 가지고 다시 get 할거임 */
resp, err = http.Get(ts.URL + "/users/" + strconv.Itoa(id))
/* int -> string 으로 바꿈 */
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
/*
읽어온거를 다시 json포맷으로 바꿔야하니까 파싱해서 다시 읽어올것
*/
user2 := new(User)
err = json.NewDecoder(resp.Body).Decode(user2)
assert.NoError(err)
assert.Equal(user.ID, user2.ID)
/*
user1 과 user2는 같아야함 -> 마지막 점검임
*/
assert.Equal(user.FirstName, user2.FirstName)
}
주석이 없는 코드는 아래의 사이트 - restful api 파일에 존재합니다.