Go 언어

본문 바로가기
사이트 내 전체검색


Go 언어
Go 언어

2. RESTful API

페이지 정보

작성자 관리자 댓글 0건 조회 1,172회 작성일 21-07-17 23:11

본문

2. RESTful API

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) 



댓글목록

등록된 댓글이 없습니다.


개인정보취급방침 서비스이용약관 모바일 버전으로 보기 상단으로

TEL. 063-469-4551 FAX. 063-469-4560 전북 군산시 대학로 558
군산대학교 컴퓨터정보공학과

Copyright © www.leelab.co.kr. All rights reserved.