2. RESTful API
페이지 정보
작성자 관리자 댓글 0건 조회 1,779회 작성일 21-07-17 23:11본문
REST를 간단하게 알아보자!
- Representational State Transfer의 약자
- 자원에 대한 CRUD를 하는데 그것을 URI에다 표시하자하는 뜻
- 구체적인 의미 : HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것을 의미한다.
CRUD Operation
- Create : 생성(POST)
- Read : 조회(GET)
- Update : 수정(PUT)
- Delete : 삭제(DELETE)
- HEAD : header 정보 조회(HEAD)
RESTful API를 구현해보자
myapp 폴더를 생성하고 간단한 handler와 test코드를 작성한다.
[jklee@www golang]$ mkdir myapp
[jklee@www golang]$ cd myapp/
[jklee@www myapp]$ go mod init myapp
go: creating new go.mod: module myapp
app.go
[jklee@www myapp]$ vi app.go
package main
import (
"fmt"
"net/http"
)
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
[jklee@www myapp]$ vi app_test.go
package main
import (
"net/http"
"testing"
"io/ioutil"
"net/http/httptest"
"github.com/stretchr/testify/assert"
)
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
}
[jklee@www myapp]$ go test
PASS
ok myapp 0.011s
[jklee@www myapp]$
이제 user ID를 Get 할 수 있게 test코드를 수정해보자.
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")
}
[jklee@www myapp]$ go test
app.go:6:2: no required module provides package github.com/gorilla/mux; to add it:
go get github.com/gorilla/mux
[jklee@www myapp]$ go get github.com/gorilla/mux
go: downloading github.com/gorilla/mux v1.8.0
go get: added github.com/gorilla/mux v1.8.0
[jklee@www myapp]$ go test
PASS
ok myapp 0.014s
[jklee@www myapp]$
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 = "jinkwan"
user.LastName = "lee"
user.Email = "leejinkwan@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":"jinkwan", "last_name":"lee", "email":"leejinkwan@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를 가져옴)
유저 정보를 저장할 수 있게 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되는 것을 볼 수 있다.
참고사이트
Creating a RESTful API With Golang | TutorialEdge.net
Go, REST API with Mux (msjo.kr)
Golang으로 웹서버 만들기(4) (velog.io)
OKKY - [GoLang] 나만의 RESTful API 서버 만들기 (1)
golang을 이용한 초 간단 restful api 만들기 (2) :: 권교의 개발 블로그 (tistory.com)
댓글목록
등록된 댓글이 없습니다.