본문 바로가기

Go

고(golang) Context 겉핥기#3

고 에서 context 패키지는 API 나 느린 동작을 다루는데 좋다. ( 특히 서버 리퀘스트 요청에서 )

context 패키지를 이해하려면 고루틴과 채널에 대한 이해가 선행 되어야 하지만 내가 쓸 예시에서는 필요가 없다.


context 를 번역하면 문맥,흐름이라는 걸로 번역이 되는데 쉽게 생각해보면
웹과 서버의 통신을 하는데에 필요한 데이터나 값을 이 context안에 넣고 필요할때 뽑아 쓰는 형식이다.

또한 그 흐름을 컨트롤 해줄수 있는 기능을 한다.

 

예를들어 3초 이상이 걸리는 http request 가 있다고 가정하자.
하지만 3초이상 걸리는 request 는 무거운 요청에 해당하기에 우리 서버에서는 그걸 허용하지 않고 싶다.

 

그럴때 고에서는 Context 를 사용한다.

 

context가 없는 코드를 짜보면

func main() {

    req, err := http.NewRequest(http.MethodGet, "http://죤내이오래걸리는데이터가져오는곳", nil)
       if err != nil {
        panic(err)
    }

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()

    longData , err := ioutil.ReadAll(res.Body)

}

죤내이오래 걸리는곳에서 request 요청을 했다. 물론 여기에서 아무런 에러는 반환하지 않겠지만 ( url이 맞다고 가정하고 )
request 를 던졌는데 그쪽에서 가져오는 시간이 30초라고 생각하면 너무 오래걸린다.

우리는 이걸 미연에 방지 하고 싶다.


context를 추가해서 request 가 2초 이상이 걸리면 에러를 반환하는 코드를 짜보자.

func main() {

    timeoutContext , cancel := context.WithTimeout(context.Background() , time.Second * 2 )
    defer cancel()

    req, err := http.NewRequestWithContext(timeoutContext,http.MethodGet,"http://죤내이오래걸리는데이터가져오는곳", nil)
       if err != nil {
        panic(err)
    }

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()

    longData , err := ioutil.ReadAll(res.Body)

}

NewRequest를 WithContext 를 추가해 위에서 정의한 2초가 지나면 에러를 반환하는 timeoutContext 를 추가해줬다.

 

func main() {

    r := gin.Default()

    r.GET("", func(c *gin.Context) {

        req , err := http.NewReqeustWithContext(c.Request.Context() , http.MethodGet, "http://google.com", nil)
        if err != nil {
            panic(err)
        }
        res, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        defer res.Body.Close()

        data , err := ioutil.ReadAll(res.Body)
        if err != nil {
            panic(err)
        }

        c.Data(200,"text/html", data)
    })
}

context 는 기본적으로 cancel을 지원한다. 위의 코드에서 만약 구글을 가져오는데 5초의 시간이 걸려서 사용자가 너무 오래 걸린다 싶어 연결을 취소하면 바로 끊어지게 된다.

 

이게 무슨말이냐면 아래의 예시를 보자.

 

아래는 context 가 없을 때 사용자가 cancel 를 보낸 로직이다.

사용자가 request 를 보내고 취소를 해도 서버의 쿼리와 데이터를 반환하는 흐름은 여전히 존재해 서버의 트래픽이 증가한다.

아래는 context 가 있을 때 사용자가 cancel 를 보낸 로직이다.
사용자가 request 를 보내고 취소를 하면 특정 쿼리와 데이터 반환하는 흐름이 없게 되어 서버의 트래픽을 줄여준다.