티스토리 뷰

golang

Golang Gin Gonic - 2. Bind data

주먹불끈 2021. 9. 17. 14:47

간단한 API 서버를 제외하고는 웹서비스의 전형적인 HTTP API server 실무에서 개발한 적이 없다. 유튜브에서 Golang 대표적인 web framework Gin 이용한 좋은 강좌를 만나 이를 하나씩 따라하려고 한다. 

 

번째는 HTTP Client server 보내는 data Gin Gonic에서 어떻게 받을 있는지 보겠다.

 

Playlist: Rest API in Golang using Gin Gonic: https://bit.ly/3hsZKbv

YouTube. How to bind data from request in Golang using Gin Gonic: https://youtu.be/OoNeWiJ1Ebk

 

구현해본 GitHub repo: https://github.com/nicewook/gin-gonic-study

이번 블로그 포스팅 소스코드: https://github.com/nicewook/gin-gonic-study/tree/main/bind-data-2

 

Server 구성

 

전체 코드는 GitHub 참고하자.

 

Gin 이용해서 개의 endpoint 구현하였다

1) GET /user 오면 Query data 받는다

2) POST /user 오면 body JSON data 받는다

3) PUT으로 3개의 URI data 받는다

4) PUT으로 1개의 URI data JSON data 받는다.

func newServer() *gin.Engine {
	r := gin.Default()
	r.GET("/user", getUserQueryHandle)
	r.POST("/user", postUserJSONHandle)
	r.PUT("/user/:id/:name/:email", putUserURIHandle)
	r.PUT("/user/:id", putUserURIJSONHandle)

	return r
}

func main() {
	newServer().Run()
}

 

각각의 핸들러와 테스트 코드

 

바인딩할 구조체를 만들어보았다. 구조체 태그에 form, uri, json 모두 넣어두었다.

각각 form Query, uri URI, json JSON 의미한다

 

type User struct {
	ID    int    `form:"id" uri:"id" json:"id"`
	Name  string `form:"name" uri:"name" json:"name"`
	Email string `form:"email" uri:"email" json:"email"`
}

 

getUserQueryHandle

Query 들어오는 data ShouldBindQuery 메서드로 바인딩한 다음, 바인딩한 user 구조체를 그대로 response 해주는 핸들러이다

 

func getUserQueryHandle(c *gin.Context) {
	var user User
	if err := c.ShouldBindQuery(&user); err != nil {
		log.Println("err: ", err)
		c.AbortWithStatus(http.StatusBadRequest)
	}
	log.Printf("user: %+v", user)
	c.JSON(http.StatusOK, gin.H{
		"status": "ok",
		"data":   user,
	})
}

 

테스트 코드는 아래와 같다.

GET request enpoint /user 뒤에 물음표를 달고 key=value 포맷으로 넣어주면 된다.

 

	t.Run("GET Query OK", func(t *testing.T) {
		resp, err := http.Get(fmt.Sprintf("%s/user?id=1&name=hsjeong&email=a@gmail.com", ts.URL))
		if err != nil {
			t.Fatalf("Expected no error, got %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Fatalf("Expected status code %v, got %v", http.StatusOK, resp.StatusCode)
		}
		printResponseBody(resp)
	})

postUserJSONHandle

 

POST reqeust body JSON ShouldBindJSON 메서드를 이용해 바인딩 해준다. 나머지 코드는 모두 같다.

 

func postUserJSONHandle(c *gin.Context) {
	var user User
	if err := c.ShouldBindJSON(&user); err != nil {
		log.Println("err: ", err)
		c.AbortWithStatus(http.StatusBadRequest)
	}
	log.Printf("user: %+v", user)
	c.JSON(http.StatusOK, gin.H{
		"status": "ok",
		"data":   user,
	})
}

테스트 코드는 POST request JSON 담아서 보낸다.

	t.Run("POST JSON OK", func(t *testing.T) {
		account := User{
			ID:    1,
			Name:  "Hyunseok, Jeong",
			Email: "a@gmail.com",
		}
		b, _ := json.Marshal(account)
		buff := bytes.NewBuffer(b)
		resp, err := http.Post(fmt.Sprintf("%s/user", ts.URL), "application/json", buff)
		if err != nil {
			t.Fatalf("Expected no error, got %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Fatalf("Expected status code %v, got %v", http.StatusOK, resp.StatusCode)
		}
		printResponseBody(resp)
	})

 

putUserURIHandle

 

이번에는 PUT request URI 왔을때의 처리이다.

 

func putUserURIHandle(c *gin.Context) {
	var user User
	if err := c.ShouldBindUri(&user); err != nil {
		log.Println("err: ", err)
		c.AbortWithStatus(http.StatusBadRequest)
	}
	log.Printf("user: %+v", user)
	c.JSON(http.StatusOK, gin.H{
		"status": "ok",
		"data":   user,
	})
}

 

Gin에서 endpoint 3 명시하였기에 3개가 아니면 에러가 난다.

	t.Run("PUT URI OK", func(t *testing.T) {
		client := &http.Client{}
		req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/user/1/hyunseokJeong/a@gmail.com", ts.URL), nil)
		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Expected no error, got %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Fatalf("Expected status code %v, got %v", http.StatusOK, resp.StatusCode)
		}
		printResponseBody(resp)
	})

 

putUserURIJSONHandle

 

가장 재미있다 있는 URI JSON 조합이다. URI 하나 있을때에 핸들러가 처리한다.

먼저 URI 바인딩 다음에 JSON 바인딩 한다. 만약 JSON ID 필드까지 포함되면 overwrite 것이다.

 

func putUserURIJSONHandle(c *gin.Context) {
	var user User
	if err := c.ShouldBindUri(&user); err != nil {
		log.Println("err: ", err)
		c.AbortWithStatus(http.StatusBadRequest)
	}
	log.Printf("user: %+v", user)

	if err := c.ShouldBindJSON(&user); err != nil {
		log.Println("err: ", err)
		c.AbortWithStatus(http.StatusBadRequest)
	}
	log.Printf("user: %+v", user)
	c.JSON(http.StatusOK, gin.H{
		"status": "ok",
		"data":   user,
	})
}

 

테스트 코드는 URI 통하여 ID 보내고, Name Email JSON 통해 보내고 있다.

 

	t.Run("PUT URI JSON", func(t *testing.T) {
		account := User{
			Name:  "Hyunseok, Jeong",
			Email: "a@gmail.com",
		}
		b, _ := json.Marshal(account)
		buff := bytes.NewBuffer(b)
		client := &http.Client{}
		req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/user/1", ts.URL), buff)
		resp, err := client.Do(req)
		if err != nil {
			t.Fatalf("Expected no error, got %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			t.Fatalf("Expected status code %v, got %v", http.StatusOK, resp.StatusCode)
		}
		printResponseBody(resp)
	})

 

참고: 테스트 결과 with richgo

 

테스트 결과가 알아보기 힘들어서 구글링으로 알아낸 패키지인데 한결 보기 쉬운 같다.

richgo 패키지: https://github.com/kyoh86/richgo

 

go get -u github.com/kyoh86/richgo 설치한 다음,

~/.zshrc alias rgo='richgo test ./…' 추가하여 테스트를 쉽게 해보았다.

 

테스트가 모두 패스하였으며, Test 코드에서 response 받은 body 출력하게 하여 data 확인할 있게 하였다.

 

$ rgo -v
START| BindDataAPI
     | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
     | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     |  - using env:    export GIN_MODE=release
     |  - using code:   gin.SetMode(gin.ReleaseMode)
     | [GIN-debug] GET    /user                     --> git-gonic-study/bind-data-2.getUserQueryHandle (3 handlers)
     | [GIN-debug] POST   /user                     --> git-gonic-study/bind-data-2.postUserJSONHandle (3 handlers)
     | [GIN-debug] PUT    /user/:id/:name/:email    --> git-gonic-study/bind-data-2.putUserURIHandle (3 handlers)
     | [GIN-debug] PUT    /user/:id                 --> git-gonic-study/bind-data-2.putUserURIJSONHandle (3 handlers)
START|   BindDataAPI/GET_Query_OK
     | 2021/09/17 13:58:58 user: {ID:1 Name:hsjeong Email:a@gmail.com}
     | [GIN] 2021/09/17 - 13:58:58 | 200 |       136.5µs |       127.0.0.1 | GET      "/user?id=1&name=hsjeong&email=a@gmail.com"
     | === CONT  TestBindDataAPI
     |     main_test.go:24: StatusCode: 200, responseBody: {"data":{"id":1,"name":"hsjeong","email":"a@gmail.com"},"status":"ok"}
START|   BindDataAPI/POST_JSON_OK
     | 2021/09/17 13:58:58 user: {ID:1 Name:Hyunseok, Jeong Email:a@gmail.com}
     | [GIN] 2021/09/17 - 13:58:58 | 200 |        44.9µs |       127.0.0.1 | POST     "/user"
     | === CONT  TestBindDataAPI
     |     main_test.go:24: StatusCode: 200, responseBody: {"data":{"id":1,"name":"Hyunseok, Jeong","email":"a@gmail.com"},"status":"ok"}
START|   BindDataAPI/PUT_URI_OK
     | 2021/09/17 13:58:58 user: {ID:1 Name:hyunseokJeong Email:a@gmail.com}
     | [GIN] 2021/09/17 - 13:58:58 | 200 |        28.6µs |       127.0.0.1 | PUT      "/user/1/hyunseokJeong/a@gmail.com"
     | === CONT  TestBindDataAPI
     |     main_test.go:24: StatusCode: 200, responseBody: {"data":{"id":1,"name":"hyunseokJeong","email":"a@gmail.com"},"status":"ok"}
START|   BindDataAPI/PUT_URI_JSON
     | 2021/09/17 13:58:58 user: {ID:1 Name: Email:}
     | 2021/09/17 13:58:58 user: {ID:0 Name:Hyunseok, Jeong Email:a@gmail.com}
     | [GIN] 2021/09/17 - 13:58:58 | 200 |        85.2µs |       127.0.0.1 | PUT      "/user/1"
     | === CONT  TestBindDataAPI
     |     main_test.go:24: StatusCode: 200, responseBody: {"data":{"id":0,"name":"Hyunseok, Jeong","email":"a@gmail.com"},"status":"ok"}
PASS | BindDataAPI (0.00s)
PASS |   BindDataAPI/GET_Query_OK (0.00s)
PASS |   BindDataAPI/POST_JSON_OK (0.00s)
PASS |   BindDataAPI/PUT_URI_OK (0.00s)
PASS |   BindDataAPI/PUT_URI_JSON (0.00s)
PASS | git-gonic-study/bind-data-2
반응형
반응형
잡학툰 뱃지
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함