쿼카러버의 기술 블로그

[Golang gin] io.NopCloser란? (ioutil.ReadAll()로 읽은 content를 날리지 않는 법) (io.ReadCloser란) 본문

[Golang]

[Golang gin] io.NopCloser란? (ioutil.ReadAll()로 읽은 content를 날리지 않는 법) (io.ReadCloser란)

quokkalover 2022. 2. 9. 23:58

Go는 바이트(bytes)를 사용하여 작업하기 위해 만들어진 프로그래밍 언어다. 근데, gin 프레임워크나 HTTP request 처리하다보면 바이트를 다루기 위한 io 패키지를 사용하다가 아찔한 함정에 빠질 수 있다.

그 함정은 ioutil.ReadAll()이나 이를 활용하는 ctx.GetRawBody()와 같은 메서드 등을 통해 request의 Body를 한 번 읽고나면 다시 읽을 수 없다는 점이다. 이는 컨텐츠를 모두 읽어냈기 때문인데, gin뿐 아니라 http패키지의 Request의 Body를 읽을 때도 마찬가지다. (당연한 소리) (io.Reader를 통해 stream을 한번 읽고나면 idx가 뒤로 가기 때문에 다시 읽을 수 없음)

 

하지만 API를 개발하다보면, Request Body의 내용을 미리 확인한 뒤, 어떤 처리를 할지 정하고, 다시 Request를 넘겨야 하는 경우가 종종 생긴다. 이럴때는 어떻게 해야 할까? 방법은 바로 `io.Nopcloser` 를 사용하는 것이다.

 

io.NopCloser란 io.ReadCloser타입을 리턴하는 메소드다. bytes.Buffer와 같이 Close()를 지원하지 않는 io.Reader를 래핑해서 no-op Close메소드(with an empty method body)를 지원하는 io.ReadCloser를 만들고 싶을 때 사용한다. (http.Request의 Body가 io.ReadCloser만 받기 때문)

 

 

바로 예시를 들어 설명하겠다.

    bodyBytes, err := ctx.GetRawData()
    fmt.Println(bodyBytes)
    bodyBytes, err = ctx.GetRawData()
    fmt.Println(bodyBytes)    

위처럼 코드를 실행하게 되면 두번째 pritn문에는 아무것도 출력이 되지 않는다.

 

다시 출력되게 하고 싶으면 아래처럼 ioutil.NopCloser를 사용하면 된다.

    bodyBytes, err := ctx.GetRawData()
    fmt.Println(bodyBytes)
    ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
    bodyBytes, err = ctx.GetRawData()
    fmt.Println(bodyBytes)

위 코드를 보면 bytes.NewBuffer라는 메소드를 사용하는데, 매개변수로 들어가는 데이터를 담은 새로운 Buffer를 만들어준다.


즉, io.NopCloser()를 통해 ReadCloser를 만들고, 컨텐츠에 사용되는 Buffer를 새로 만들어서 넣어주는 것이다.

이렇게 하면, 다시 ctx.GetRawData()나, Body를 ioutil.ReadAll()로 읽고난 뒤에도 다시 사용할 수 있다.

 

참고로 go 1.16버전 부터는 io.NopCloser, 이전 버전은 ioutil.Nocloser를 사용해야 한다.

 

 

참고자료:
https://medium.com/@xoen/golang-read-from-an-io-readwriter-without-loosing-its-content-2c6911805361

Comments