记录通过HTTP客户端发送的所有HTTP请求和响应

huangapple go评论76阅读模式
英文:

Logging All HTTP Request and Response from done through an HTTP Client

问题

我有以下简单的http.Client

import (
    "net/http"
    "log"
)
...
func main() {
    ...
    link = "http://example.com"
    method = "GET"
    req, _ := http.NewRequest(method, link, nil)

    client := &http.Client{}

    myZapLogger.Info("发送一个 %s 请求到 %s\n", method, link)

    resp, err := client.Do(req)
    if err != nil {
        myZapLogger.Error(..., err) // 我记录而不是致命错误或其他
    } else { 
        myZapLogger.Info("收到请求 X 的 %d\n", resp.StatusCode)
    }
    ...
}

...

我正在寻找一种通过钩子(或其他方式)为每个请求执行上述操作的方法,以便每次自动触发。我可以编写一个函数来封装所有这些操作,但在将http客户端传递给其他包的情况下,我将无法以这种方式控制/记录此类请求(例如aws-go-sdk)。

是否可以通过上下文或将钩子附加到客户端来实现这一点?

谢谢

英文:

I have the following simple http.Client:

import (
 "net/http"
 "log"
)
...
func main() {
   ...
   link = "http://example.com"
   method = "GET"
   req, _ := http.NewRequest(method, link, nil)

   client := &http.Client{}

   myZapLogger.Info("Sending a %s request to %s\n", method, link)

   resp, err := client.Do(req)
   if err != nil {
      myZapLogger.Error(..., err) // I'm logging rather than fatal-ing or so
   } else { 
      myZapLogger.Info("Received a %d on request X", resp.StatusCode)
   }
   ...
}

...

I was looking for a way to do the above for each request through a hook (or so), so that it's triggered automatically each time. I can write a function the encloses all that, but in a case where I'm passing an http client to some other package, I wouldn't be able to control/log such requests that way (e.g. aws-go-sdk).

Is there a way to do this through contexts or attaching hooks to the client?

Thanks

答案1

得分: 1

eudore的评论回答了这个问题;我只是将其转化为代码:

type MyRoundTripper struct {}

func (t MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	
	// 在发送请求之前进行操作

	resp, err := http.DefaultTransport.RoundTrip(req)
	if err != nil {
		return resp, err
	}
	
	// 在接收到响应之后进行操作

	return resp, err
}

要使用它,只需将其传递给你的HTTP客户端:

rt := MyRoundTripper{}
client := http.Client{Transport: rt}
英文:

eudore's comment answers the question; I'll just put it into code:

type MyRoundTripper struct {}

func (t MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	
	// Do work before the request is sent

	resp, err := http.DefaultTransport.RoundTrip(req)
	if err != nil {
		return resp, err
	}
	
	// Do work after the response is received

	return resp, err
}

To use it, you'll just pass it to your HTTP Client:

rt := MyRoundTripper{}
client := http.Client{Transport: rt}

答案2

得分: 0

我已经混合了几个不同的地方,并编写了这段代码:

package matrix

import (
	"bytes"
	"mylogger/colorlogger"
	"io"
	"net/http"
)

func filterAuthorization(headers http.Header) http.Header {
	filteredHeaders := make(http.Header)
	for k, v := range headers {
		if k == "Authorization" {
			filteredHeaders[k] = []string{"<removed>"}
		} else {
			filteredHeaders[k] = v
		}
	}
	return filteredHeaders
}

type MyRoundTripper struct{}

func (t MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	var reqStr string
	var respStr string

	if req.Body != nil {
		reqbuf, e := io.ReadAll(req.Body)
		if e != nil {
			colorlogger.Log.Fatal("从 RoundTrip 注入器读取 HTTP 响应时出错:", e)
		} else {
			reqrdr1 := io.NopCloser(bytes.NewBuffer(reqbuf))
			reqrdr2 := io.NopCloser(bytes.NewBuffer(reqbuf))
			req.Body = reqrdr2
			// 将 resp.Body 读取为字符串
			breq, errreq := io.ReadAll(reqrdr1)
			if errreq != nil {
				colorlogger.Log.Error("从 RoundTrip 注入器读取 HTTP 请求时出错:", errreq)
			} else {
				reqStr = string(breq)
			}
		}
	}

	// 在发送请求之前进行处理
	resp, err := http.DefaultTransport.RoundTrip(req)
	if err != nil {
		return resp, err
	}

	if resp.Body != nil {
		respbuf, e := io.ReadAll(resp.Body)
		if e != nil {
			colorlogger.Log.Fatal("从 RoundTrip 注入器读取 HTTP 响应时出错:", e)
		} else {
			resprdr2 := io.NopCloser(bytes.NewBuffer(respbuf))
			resp.Body = resprdr2
			resprdr1 := io.NopCloser(bytes.NewBuffer(respbuf))
			// 将 resp.Body 读取为字符串
			b, errresp := io.ReadAll(resprdr1)
			if errresp != nil {
				colorlogger.Log.Error("从 RoundTrip 注入器读取 HTTP 响应时出错:", errresp)
			} else {
				respStr = string(b)
			}
		}
	}

	// 在接收到响应后进行处理
	colorlogger.Log.Debugf("%s请求(%s) %s, Headers: %+v, Body: %s, 响应(%d), Headers: %+v, Body: %s%s",
		colorlogger.Blue,
		req.Method,
		req.URL.Path,
		filterAuthorization(req.Header),
		reqStr,
		resp.StatusCode,
		resp.Header,
		respStr,
		colorlogger.CLR)

	return resp, err
}

如下所示,这是如何集成的:

要使用它,只需将其传递给您的 HTTP 客户端:

rt := MyRoundTripper{}
client := http.Client{Transport: rt}

**注意:**由于存在“fatal”错误处理,我可能会稍微更改错误处理方式。

**注意:**我也喜欢 https://medium.com/@motemen/add-a-single-line-to-your-go-source-and-track-every-http-requests-responses-37f2f15eb72f,但我希望能够在运行时从调试模式切换到非调试模式。

英文:

I've remixed a few different places and came up with this code:

package matrix
import (
&quot;bytes&quot;
&quot;mylogger/colorlogger&quot;
&quot;io&quot;
&quot;net/http&quot;
)
func filterAuthorization(headers http.Header) http.Header {
filteredHeaders := make(http.Header)
for k, v := range headers {
if k == &quot;Authorization&quot; {
filteredHeaders[k] = []string{&quot;&lt;removed&gt;&quot;}
} else {
filteredHeaders[k] = v
}
}
return filteredHeaders
}
// https://stackoverflow.com/questions/23070876/reading-body-of-http-request-without-modifying-request-state
// https://stackoverflow.com/questions/69276445/logging-all-http-request-and-response-from-done-through-an-http-client
type MyRoundTripper struct{}
func (t MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
var reqStr string
var respStr string
if req.Body != nil {
reqbuf, e := io.ReadAll(req.Body)
if e != nil {
colorlogger.Log.Fatal(&quot;Reading http response from RoundTrip injector error: &quot;, e)
} else {
reqrdr1 := io.NopCloser(bytes.NewBuffer(reqbuf))
reqrdr2 := io.NopCloser(bytes.NewBuffer(reqbuf))
req.Body = reqrdr2
// read resp.Body to string
breq, errreq := io.ReadAll(reqrdr1)
if errreq != nil {
colorlogger.Log.Error(&quot;Reading http request from RoundTrip injector error: &quot;, errreq)
} else {
reqStr = string(breq)
}
}
}
// Do work before the request is sent
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return resp, err
}
if resp.Body != nil {
respbuf, e := io.ReadAll(resp.Body)
if e != nil {
colorlogger.Log.Fatal(&quot;Reading http response from RoundTrip injector error: &quot;, e)
} else {
resprdr2 := io.NopCloser(bytes.NewBuffer(respbuf))
resp.Body = resprdr2
resprdr1 := io.NopCloser(bytes.NewBuffer(respbuf))
// read resp.Body to string
b, errresp := io.ReadAll(resprdr1)
if errresp != nil {
colorlogger.Log.Error(&quot;Reading http response from RoundTrip injector error: &quot;, errresp)
} else {
respStr = string(b)
}
}
}
// Do work after the response is received
colorlogger.Log.Debugf(&quot;%sREQUEST(%s) %s, Headers: %+v, Body: %s, RESPONSE(%d), Headers: %+v, Body: %s%s&quot;,
colorlogger.Blue,
req.Method,
req.URL.Path,
filterAuthorization(req.Header),
reqStr,
resp.StatusCode,
resp.Header,
respStr,
colorlogger.CLR)
return resp, err
}

And as show below, this is integrated like this:

> To use it, you'll just pass it to your HTTP Client:
>
> rt := MyRoundTripper{} client := http.Client{Transport: rt}

Note: I might change error handling a bit because of the fatal.

Note: I also liked https://medium.com/@motemen/add-a-single-line-to-your-go-source-and-track-every-http-requests-responses-37f2f15eb72f but I'd like to be able to change from debug to none-debug during runtime later.

huangapple
  • 本文由 发表于 2021年9月22日 07:06:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/69276445.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定