구리의 창고

Golang - http transport RoundTripper 사용 본문

Golang

Golang - http transport RoundTripper 사용

구리z 2017. 7. 7. 23:19

개요

Golang의 http 패키지는 많은 편리한 기능을 들고 있다. 그 중 하나인 RoundTripper에 대해 소개해보려고한다.
요즘 개발을 하다보면 REST API 같은 http/https을 사용한 수 많은 라이브러리들을 접할 수 있다. 그리고 그 많은 API들은 전후처리가 연속적으로 필요한 경우가 생긴다.

전후처리에 대한 한 가지 예로, API의 Latency를 로깅하고 싶을 수 있다. 혹은 Basic Authentication이 필요한 API 들도 있다.
이를 좀 더 편하게 해주는게 RoundTripper다. 이름 그대로 코딩된 RoundTripper 순서대로 http 요청하기 전에 뭔가 작업을 할 수가 있다.
마치 미들웨어를 사용하는 느낌이드는 녀석이다.

긴 설명보다 코드가 이해하기 더 좋다. 아래 코드를 보자.

RoundTripper

RoundTripper는 Interface이다. 문서를 살펴보면 RoundTrip(*Request) (*Response, error)를 구현해줘야한다.
type RoundTripper interface {
  	RoundTrip(*Request) (*Response, error)
}

예제 코드1 (RoundTripper 사용 전)

위에 든 예 중, 간단하게 API Latency 로깅하는 것을 작성해보겠다.
일반적으로 아래처럼, 시간 체크를 앞 뒤로 두고 코드를 작성 할 수 있다.
package main

import (
        "fmt"
        "io/ioutil"
        "net/http"
        "time"
)

func main() {
        client := http.DefaultClient

        now := time.Now()

        url := "https://ipinfo.io/8.8.8.8/json"
        resp, err := client.Get(url)
        if err != nil {
                panic(err)
        }
        defer resp.Body.Close()

        fmt.Printf("[DEBUG] url: %s, latency: %f seconds\n", url, time.Since(now).Seconds())

        body, err := ioutil.ReadAll(resp.Body)
        fmt.Println(string(body))
}
위 코드를 실행하면 다음과 같은 결과가 나온다.
[DEBUG] url: https://ipinfo.io/8.8.8.8/json, latency: 1.011706 seconds
{
  "ip": "8.8.8.8",
  "city": "Mountain View",
  "region": "California",
  "country": "US",
  "loc": "37.3860,-122.0840",
  "org": "AS15169 Google Inc.",
  "postal": "94035",
  "phone": "650"
}

예제 코드2 (RoundTripper 사용)

RoundTripper를 사용한 예제코드다.
http.DefaultClient의 Transport를 작성한 latencyTransport로 변경했다.
latencyTransport.RoundTrip에서는 맴버 변수로 있는 transport를 실행하고 Latency를 계산한다.
package main

import (
        "fmt"
        "io/ioutil"
        "net/http"
        "time"
)

func main() {
        latencyTransport := newLatencyTransport(http.DefaultTransport)

        client := http.DefaultClient
        client.Transport = latencyTransport

        resp, err := client.Get("https://ipinfo.io/json")
        if err != nil {
                panic(err)
        }
        defer resp.Body.Close()

        body, err := ioutil.ReadAll(resp.Body)
        fmt.Println(string(body))
}

type latencyTransport struct {
        transport http.RoundTripper
}

func newLatencyTransport(t http.RoundTripper) http.RoundTripper {
        return &latencyTransport{t}
}

func (t *latencyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
        now := time.Now()
        resp, err := t.transport.RoundTrip(req)
        if err != nil {
                return resp, err
        }
        fmt.Printf("[DEBUG] url: %s, latency: %f seconds\n", req.URL, time.Since(now).Seconds())

        return resp, err
}
위 코드를 실행하면 역시 같은 결과가 나온다.
[DEBUG] url: https://ipinfo.io/8.8.8.8/json, latency: 1.011706 seconds
{
  "ip": "8.8.8.8",
  "city": "Mountain View",
  "region": "California",
  "country": "US",
  "loc": "37.3860,-122.0840",
  "org": "AS15169 Google Inc.",
  "postal": "94035",
  "phone": "650"
}

정리

위에 든 예는 블로그에 정리하기 위한 매우 간단한 경우다. 가장 유용하게 사용 할 수 있는 곳은 인증이 필요한 API라고 생각이된다. 아마 아래와 같은 플로우가 그려질 수 있을 것 같다.

1. API 요청
2. API 인증 실패 응답
3. API 인증 및 토큰 획득
4. API 토큰과 함께 다시 재시도

각 과정을 RoundTripper로 작성하게 된다면, 좀 더 깔끔하고 재활용 가능한 코드가 되지 않을까 싶다.

docker-registry-client 오픈소스에서 registry 인증 시 위와 비슷한 루틴으로 구현되어있다.
좀 더 자세한 코드를 보고 싶으면 https://github.com/heroku/docker-registry-client를 살펴보자.


Comments