프로그래밍(Web)/Golang

[바미] Go - 간단한 JSON Transfer 만들기.

Bami 2020. 12. 16. 15:58
728x90
반응형

Web Handler에 이어서 main함수의

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello Bar!")
})

부분 중 fooHandler처럼 func(w http.ResponseWriter, r *http.Request) 부분을 바깥으로 빼서 수정 해줍니다.

  func barHandeler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello Bar!")
  })

이렇게 수정한 뒤 main함수에 이에 해당하는 코드를 수정해줍니다.

  http.HandleFunc("/bar", barHandler)

이렇게 각각 경로에 해당하는 Handler들을 등록했습니다. 이렇게 경로에 따라 분배해주는 라우터가 있는데 "Mux"라는 것입니다.

그래서 main함수에 라우터 클래스를 만들어서 등록하는 방식으로 수정 해봅시다.

    func main() {
      mux := http.NewServeMux()
      mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello World")
      })

      mux.HandleFunc("/bar", barHandler)

      mux.Handle("/foo", &fooHandler{})

      http.ListenAndServe(":3000", mux)
  }

이 상태에서 실행을 해도 전과 똑같은 화면이 출력됩니다.

실행화면
실행화면
실행화면

이 전과 차이가 있다면 기존에는 HTTP에 정적으로 등록했는데 지금은 mux라는 인스턴스를 만들어서 거기에 등록해서

그 인스턴스를 넘겨주는 방식으로 바꾸었습니다.

 

우리가 서버에 요청할 때 request를 날리는데 이 request안에 필요한 argument의 input값을 넣을 수 있는데

  func barHandler(w http.ResponseWriter, r *http.Request) {
      name := r.URL.Query().Get("name") // 1
      if name == "" { // 2
        name = "World"
      }
      fmt.Fprintf(w, "Hello %s!", name) // 3
  }

1 : r.URL.Query()는 URL에서 정보를 뽑아내기 위해 사용하는 것이고, 그 결과에서 name이라는 argument를 Get하기 위해 r.URL.Query().Get("name")으로 작성했습니다.
즉, URL에서 name이라는 argument를 뽑아내서 그 값을 name에 넣어줍니다.

2 : 그 후 name값이 없으면 name변수에 World가 들어가도록 해줍니다.

3 : Fprintf를 사용하여 출력 시켜줍니다.

 

이 상태에서 실행 시켜 봅시다.

먼저 /bar경로로 들어가면 "Hello world!"가 출력되는 것을 볼 수 있는데

실행화면

이 상태에서 ?name=bar를 경로에 추가 시켜주게 되면

실행화면

"Hello bar!"가 출력 되는 것을 알 수 있습니다.

즉, name=임의적인name값 을 넣으면 Hello 임의적인name 값이 출력됨을 알 수 있습니다.

 

이제부터 JSON을 다루기 위해 User라는 struct를 만들어 봅시다.

   type User struct {
      FirstName string
      LastName  string
      Email     string
      CreatedAt time.Time
    }

그리고 fooHandler에서 Request값이 JSON으로 오기 때문에 그 것을 읽을 수 있도록 fooHandler부분을 수정해줍니다.

   func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      user := new(User) // 1
      err := json.NewDecoder(r.Body).Decode(user) // 2
      if err != nil { // 3
        w.WriteHeader(http.StatusBadRequest)
        fmt.Fprint(w, "Bad Request: ", err)
        return
      }
      user.CreatedAt = time.Now() // 4

      data, _ := json.Marshal(user) // 5
      w.WriteHeader(http.StatusOK) // 6
      fmt.Fprint(w, string(data))
  }

1 : User struct형태의 JSON이 올 것이기 때문에 그 값을 채워줄 인스턴스를 만들어 줍니다.

2 : 이 값을 JSON형태로 파싱을 해주어야 하는데 그것을 위해 NewDecoder(r.Body)를 사용하고, JSON형태의 데이터가 Body에 들어있기 때문에 NewDecoder의 인자로 reader인 r을 받기 때문에 r.Body를 넣어줍니다.

r.Body에 커서를 두게 되면

r.Body io.Reader를 포함하고 있음을 알 수 있고, NewDecoderio.Reader를 인자로 받고 있다는 걸 알 수 있습니다.
그리고 Body에서 값을 읽어서 user struct형태로 값을 채워주기 위해 Decode(user)를 사용합니다.
이 부분은 뭔가 데이터가 잘못될 수 있기 때문에 err변수에 넣어줍니다.

 

3 : 그래서 if문으로 err가 nil이 아닌경우에는 에러가 생겼기 때문에 w.WriteHeader(http.StatusBadRequest)로 잘못 보냈다라는것을 알려주고, fmt.Fprint(w, "Bad Request: ", err)로 Body에 쓸 수 있도록 작성한 후 더 이상 에러가 나지 않도록 return 시켜줍니다.

4 : 성공적으로 Decode가 됐을 때 err값이 nil이 되는데 그 때 user의 CreatedAt값을 현재로 바꾸어 주고,

5 : JSON을 다시 보내주려면 JSON data로 바꾸어 주어야 합니다. 지금 상태는 Go struct형태이기 때문인데 어떤 인터페이스를 받아서 JSON형태로 Encodding을 해주는 Marshal(user)를 사용해줍니다.
이 형태는 첫 번째 리턴 값으로 byte array가 나오고 두 번째 return값으로 error가 나오는데 error를 무시하기 위해 작성 해주었습니다.

6 : 잘 되었다고 알려주기 위해 사용하는데 string(data)으로 사용한 이유는 data가 byte[]형식이기 때문에 string으로 형변환 시켜준 것입니다.

 

이 상태에서 실행을 하게 되면

실행화면

EndOfFile이라는 것이 뜬다. Body에 data를 넣어야 하기 때문에 URL에 데이터를 넣어도 결과는 똑같습니다.

이럴 때 필요한 것이 클라이언트 앱입니다. 크롬 앱 중에 Advanced REST client 라는 앱을 설치 해봅시다.

구글 크롬앱 화면

앱을 키고 실행해줍시다.

POST로 바꾸어주고, 주소는 http://localhost:3000/foo로 지정해준 뒤, Body 부분에 user struct부분에 맞추어 JSON형태의 값을 넣어준 뒤 데이터를 날려줍니다.

결과를 보면

{"FirstName":"","LastName":"","Email":"changbeom@naver.com","CreatedAt":"2020-09-16T13:49:06.990073+09:00"}

Email과 시간을 제외한 모든 값들이 제대로 들어가지 않았습니다.

그 이유는 JSON에 입력했던 형식과 User struct에서 쓰는 형식이 다르기 때문에 서로 다르게 인식하는 것인데,

그것을 맞춰주어야 합니다.

   type User struct {
    FirstName string 
    LastName  string
    Email     string
    CreatedAt time.Time
  }

여기에서 FirstName을 first_name으로 바꾸어 주어도 되지만 Go에서는 밑줄을 싫어합니다. 

그렇기 때문에 이런식으로 옆에 Annotation(설명)을 붙여줍니다.

   type User struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Email     string `json:"email"`
    CreatedAt time.Time `json:"created_at"`
  }

이렇게 하면 Decode하고 Marshal할 때 해당 컬럼 이름에 맞추어서 해줍니다.

다시 실행해봅시다!

제대로 들어간 것을 확인할 수 있습니다.
하지만 우리가 입력했던 형식이랑은 다르게 출력이 되었는데 그 이유는 JSON이 아닌 TEXT로 인식하고 있기 때문입니다.

 

그래서 func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) 부분에
w.Header().Add("content-type", "application/json")을 추가해준 뒤 다시 실행 시키면

정상적으로 동작하는 것을 확인 할 수 있습니다.

풀 소스


  package main

  import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
  )

   type User struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Email     string `json:"email"`
    CreatedAt time.Time `json:"created_at"`
  }

  type fooHandler struct{}

  func (f *fooHandler) ServeHTTP(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, "Bad Request: ", err)
      return
    }

    user.CreatedAt = time.Now()
    data, _ := json.Marshal(user)
    w.Header().Add("content-type", "application/json")
        w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, string(data))
  }

  func barHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    if name == "" {
      name = "World"
    }
    fmt.Fprintf(w, "Hello %s!", name)
  }

  func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      fmt.Fprint(w, "Hello World")
    })

    mux.HandleFunc("/bar", barHandler)

    mux.Handle("/foo", &fooHandler{})

    http.ListenAndServe(":3000", mux)
  }

 

출처 : Turker의 Golang

728x90
반응형