728x90

Go HTTP 클라이언트는 응답 상태와 헤더를 Network socket 으로 부터 읽고, 읽은 데이터는 응답 객체에서 바로 사용할 수 있습니다. 하지만 클라이언트가 자동으로 Response body를 읽지는 않습니다.

 

Response body는 명시적으로 코드상에서 읽기 전까지는 소비되지 않은 상태로 남아 있으며, Response body를 닫게되면 읽지 않은 바이트를 그대로 소비합니다.

 

Go HTTP 클라이언트가 socket을 닫을 때 암묵적으로 Response Body를 소비하는 것이 잠재적으로 문제가 될 수 있습니다.

 

예를 들어, GET method 로 파일을 요청하고 서버로부터 응답을 받았습니다.

응답의 Content-Length 헤더를 읽었는데, 파일이 기대한 것보다 훨씬 크다는 것을 알았습니다.

그래서 아무런 바이트를 읽지 않은 채로 Response Body를 닫아버림에도 불구하고 Go는 Response Body를 소비하기 위해 서버로 부터 전체 파일을 다운로드 할 것입니다.

 

더 나은 선택은 HEAD 요청을 보내어 Content-Length 헤더의 값을 얻어 오는 것입니다.

 

그러면 Response body 내에 읽지않은 바이트가 존재하지 않게 되며 Response body를 닫더라도 소비시에 추가로 발생하는 오버헤드가 존재하지 않습니다.

 

func TestHeadTime(t *testing.T) {
	resp, err := http.Head("https://www.time.gov")
	if err != nil {
		t.Fatal(err)
	}

	_ = resp.Body.Close() // 예외 상황처리 없이 항상 body 를 닫습니다.
	/**
	Go lang 에서 response body 를 닫으면 자동으로 모든 byte 를 소비하여 재사용 할수 있게 합니다.
	모든 response body 를 닫는것은 TCP 세션을 재사용하기 위해 중요합니다.
	*/

	now := time.Now().Round(time.Second)
	date := resp.Header.Get("Date")
	if date == "" {
		t.Fatal("no Date header received from time.gov")
	}

	dt, err := time.Parse(time.RFC1123, date)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("time.gov: %s (skew %s)", dt, now.Sub(dt))
}

 

 

위 코드와 같이 Response body를 적절하게 닫았기에 향후에 Go HTTP client 가 TCP 세션을 재사용 할수있습니다.

 

종종 HTTP 요청을 하였는데 명시적으로 Response body를 소비해야하는 경우, 가장 효율적인 방법은 io.Copy method를 이용하는것 입니다.

 

_, _ = io.Copy(ioutil.Discard, rsponse.Body)\
_ = response.Close()

 

io.Copy 함수는 response body의 모든 바이트를 읽어서 ioutil.Discard 에 전부 쓰는 형태로 response body를 모두 소비합니다.

이름에서 알 수 있듯이 ioutils.Discard 함수는 쓰는 모든 데이터를 버려 버리는 특별한 io.Writer 입니다

 

io.Copy method 와 response.Close method 의 반환값을 받지않아서 무시해도 되지만 언더스코어 를 통해 값을 무시함으로써 명시적으로 무시하고있다는 것을 드러냅니다.

 

결론적으로 response body 를 읽든 또는 읽지않든 리소스가 낭비되는 것을 막기위해선 항상 Close 해줘야 합니다.

728x90