Open robert-min opened 1 year ago
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
type LoggingClient struct {
log *log.Logger
}
// Logging outgoing requests
func (c LoggingClient) RoundTrip(r *http.Request) (*http.Response, error) {
c.log.Printf("Sending a %s request to %s over %s\n", r.Method, r.URL, r.Proto)
resp, err := http.DefaultTransport.RoundTrip(r)
c.log.Printf("Got back a response over %s\n", resp.Proto)
return resp, err
}
func fetchRemoteResource(client *http.Client, url string) ([]byte, error) {
r, err := client.Get(url)
if err != nil {
return nil, err
}
defer r.Body.Close()
return io.ReadAll(r.Body)
}
func createHTTPClientWithTimeout(d time.Duration) *http.Client {
client := http.Client{Timeout: d}
return &client
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stdout, "Must specify a HTTP URL to get data from")
os.Exit(1)
}
myTransport := LoggingClient{}
// Create log.Logger object, 로그가 작성될 io 객체
l := log.New(os.Stdout, "", log.LstdFlags)
myTransport.log = l
client := createHTTPClientWithTimeout(15 * time.Second)
client.Transport = &myTransport
body, err := fetchRemoteResource(client, os.Args[1])
if err != nil {
fmt.Fprintln(os.Stdout, "%#v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "Bytes in response: %d\n", len(body))
}
type AddHeadersMiddleware struct {
headers map[string]string
}
func (h AddHeadersMiddleware) RoundTrip(r *http.Request) (*http.Response, error) {
reqCopy := r.Clone(r.Context())
for k, v := range h.headers {
reqCopy.Header.Add(k, v)
}
return http.DefaultTransport.RoundTrip(reqCopy)
}
func createClient(headers map[string]string) *http.Client {
h := AddHeadersMiddleware{
headers: headers,
}
client := http.Client{
Transport: &h,
}
return &client
}
func startHTTPServer() *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
w.Header().Set(k, v[0])
}
fmt.Fprint(w, "I am the Request Header echoing program")
}))
return ts
}
func TestAddHeaderMiddleware(t *testing.T) {
testHeaders := map[string]string{
"X-Client-Id": "test-client",
"X-Auth-Hash": "random$string",
}
client := createClient(testHeaders)
ts := startHTTPServer()
defer ts.Close()
resp, err := client.Get(ts.URL)
if err != nil {
t.Fatalf("Expected non-nil error, got: %v", err)
}
for k, v := range testHeaders {
if resp.Header.Get(k) != testHeaders[k] {
t.Fatalf("Expected header: %s:%s, Got: %s:%s", k, v, k, testHeaders[k])
}
}
}
net/http
라이브러리를 통해 자동으로 관리
func createHTTPClientWithTimeout(d time.Duration) *http.Client {
client := http.Client{Timeout: d}
return &client
}
func createHTTPGetRequestWithTrace(ctx context.Context, url string) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
trace := &httptrace.ClientTrace{
// DNS lookup for a specific hostname is completed
DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
fmt.Printf("DNS Info: %+v\n", dnsInfo)
},
// Already existing TCP connection from connection pooling
GotConn: func(connInfo httptrace.GotConnInfo) {
fmt.Printf("Got Conn: %+v\n", connInfo)
},
TLSHandshakeStart: func() {
fmt.Printf("TLS HandShake Start\n")
},
TLSHandshakeDone: func(connState tls.ConnectionState, err error) {
fmt.Printf("TLS HandShake Done\n")
},
PutIdleConn: func(err error) {
fmt.Printf("Put Idle Conn Error: %+v\n", err)
},
}
ctxTrace := httptrace.WithClientTrace(req.Context(), trace)
req = req.WithContext(ctxTrace)
return req, err
}
func main() {
d := 5 * time.Second
ctx := context.Background()
client := createHTTPClientWithTimeout(d)
req, err := createHTTPGetRequestWithTrace(ctx, os.Args[1])
if err != nil {
log.Fatal(err)
}
for {
resp, _ := client.Do(req)
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
fmt.Printf("Resp protocol: %#v\n", resp.Proto)
time.Sleep(1 * time.Second)
fmt.Println("--------")
}
}
로깅 미들웨어
참고. 클라이언트 미들웨어 구현
참고. 클라이언트 객체 커스터마이징을 통한 커스텀 미드웨어 구현 RoundTripper
http.Client
구조체의 Trasport 필드는 RoundTripper 인터페이스로 구현