Go에서 http.Request/http.Response를 Raw String으로 만들기

오늘은 golang에서 http.Request를 Raw HTTP Request (string) 형태로 치환하는 방법에 대해 메모하려고 합니다.

http.Request and http.Response

http.Request 그리고 http.Response는 golang의 기본 http 패키지에서 제공하는 struct로 HTTP Request와 Response를 http client, 웹 서버 프레임워크들(echo, gin 등)에서 사용할 수 있도록 제공되고 있습니다.

  • https://pkg.go.dev/net/http#Request
  • https://pkg.go.dev/net/http#Response

다만 struct 내부에 Raw HTTP Request/Response를 담고있진 않아서 막상 아래와 같은 포맷으로 출력하려고 하면 고민이 생기게 됩니다. (이걸..어떻게 출력하는게 좋을까)

type Request struct {
        Method string
        URL *url.URL
        Header Header
        Body io.ReadCloser
        GetBody func() (io.ReadCloser, error)
        ContentLength int64
        TransferEncoding []string
        Close bool
        Host string
        Form url.Values
        PostForm url.Values
        MultipartForm *multipart.Form
        Trailer Header
        RemoteAddr string
        RequestURI string
        TLS *tls.ConnectionState
        Cancel <-chan struct{}
        Response *Response
}

마치 이렇게 처리하고 싶은거죠.

GET / HTTP/1.1
Host: www.hahwul.com
X-API-Key: 123412414

httputil

가장 간단한 방법으로는 httputil 패키지를 사용하는 것입니다. httputil에는 DumpRequest(), DumpRequestOut(), DumpResponse()란 함수가 있습니다. 이는 http.Request, http.Response를 우리가 보통 아는 HTTP Raw Request 형태의 string으로 변환해주는 함수입니다.

requestDump, err := httputil.DumpRequestOut(req, true)
if err != nil {
  fmt.Println(err)
}
fmt.Println(string(requestDump))

DumpRequest

http.Request를 HTTP 전문 []byte 형태로 리턴해줍니다. 다만 모든 정보가 포함되는건 아니라서, 전체 데이터 사용을 위해선 아래 DumpRequestOut이 조금 더 좋을 것 같네요.

func DumpRequest(req *http.Request, body bool) ([]byte, error){

}

https://pkg.go.dev/net/http/httputil#DumpRequest

DumpRequestOut

DumpRequest에서 실제 전송을 위한 데이터가 추가된 함수입니다. UA등이 빠지지 않고 모두 포함되어 리턴됩니다.

func DumpRequestOut(req *http.Request, body bool) ([]byte, error){

}

https://pkg.go.dev/net/http/httputil#DumpRequestOut

DumpResponse

func DumpResponse(resp *http.Response, body bool) ([]byte, error){

}

https://pkg.go.dev/net/http/httputil#DumpResponse

string+string+string

또 다른 방법으로는 http.Request, http.Response에서 담고 있는 Method, Protocol 등을 직접 출력하여 HTTP Request/HTTP Response 전문을 만드는 방법입니다. 다만 직접 데이터를 처리해야하기 때문에 썩 좋은 방법은 아니지만 때때로 아래 요청과 같이 약간 다른 형태의 HTTP Request를 만들어야할 때에는 차용할 수 있는 방법입니다.

GET https://www.hahwul.com HTTP/1.1
Host: www.hahwul.com
X-API-Key: 1234

맨 위의 http.Request, http.Response의 struct를 보시면 아시겠지만, Raw HTTP Request 구성에 필요한 모든 정보는 다 가지고 있습니다. 이것을 직접 핸들링하기만 하면 되는데요. 아무래도 type에 구애받지 않고 출력하기에는 springf가 편리하기 때문에 이를 통해 HTTP 전문을 만들 수 있습니다.

func RequestToString(r *http.Request) string {
 var request []string
 url := fmt.Sprintf("%v %v %v", r.Method, r.URL, r.Proto)
 request = append(request, url)
 request = append(request, fmt.Sprintf("Host: %v", r.Host))
 for name, headers := range r.Header {
   name = strings.ToLower(name)
   for _, h := range headers {
     request = append(request, fmt.Sprintf("%v: %v", name, h))
   }
 }

 if r.Method == "POST" {
    r.ParseForm()
    request = append(request, "\n")
    request = append(request, r.Form.Encode())
 }

  return strings.Join(request, "\n")
}

단 이렇게 찍게되면 놓치게 되는 데이터가 발생할 수 있어서 가급적이면 httputil을 쓰는것이 훨씬 좋겠네요.

References