robert-min / handson-go

Go-lang hands on guide
0 stars 0 forks source link

Chapter4. HTTP client - Logging middleware, Header middleware, Connection pool #12

Open robert-min opened 1 year ago

robert-min commented 1 year ago

로깅 미들웨어

참고. 클라이언트 미들웨어 구현

참고. 클라이언트 객체 커스터마이징을 통한 커스텀 미드웨어 구현 RoundTripper

robert-min commented 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))
}
robert-min commented 1 year ago

Header 미들웨어

참고. Golang decorator 링크

robert-min commented 1 year ago

Hedaer 미들웨어 코드 구현


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
}
robert-min commented 1 year ago

Header 미들웨어 테스트 코드


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])
        }
    }
}
robert-min commented 1 year ago

Connection pool

robert-min commented 1 year ago

Connection pool 동작 구현


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("--------")
    }
}
스크린샷 2023-05-29 오후 3 19 28