오늘은 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
- https://pkg.go.dev/net/http#Request
- https://pkg.go.dev/net/http/httputil
- https://pkg.go.dev/net/http/httputil#DumpRequest
- https://pkg.go.dev/net/http/httputil#DumpRequestOut
- https://pkg.go.dev/net/http/httputil#DumpResponse