美文网首页
Golang http.RoundTripper

Golang http.RoundTripper

作者: 合肥黑 | 来源:发表于2019-03-26 14:41 被阅读0次

    参考
    Golang http.RoundTripper 笔记

    RoundTripper is an interface representing the ability to execute a single HTTP transaction, obtaining the Response for a given Request.

    对于http客户端,可以使用不同的实现了 RoundTripper 接口的Transport实现来配置它的行为。RoundTripper 有点像 http.Client 的中间件

    一、接口定义
    type RoundTripper interface { 
           RoundTrip(*Request) (*Response, error)
    }
    

    实现比如

    type SomeClient struct {}
    
    func (s *SomeClient) RoundTrip(r *http.Request)(*http.Response, error) {
        //Something comes here...Maybe
        return nil,nil
    }
    
    二、应用场景

    参考https://lanre.wtf/blog/2017/07/24/roundtripper-go/
    1.缓存 responses,比如 app需要访问 Github api,获取 trending repos,这个数据变动不频繁,假设30分钟变动一次,你显然不希望每次都要点击api都要来请求Github api,解决这个问题的方法是实现这样的http.RoundTripper

    • 有缓存时从缓存取出response数据
    • 过期,数据通过重新请求api获取

    2.根据需要设置http header, 一个容易想到的例子go-github一个Github的 api的go客户端。某些github api不需要认证,有些需要认证则需要提供自己的http client,比如 ghinstallation,下面是ghinstallation 的 RoundTrip 函数实现,设置 Authorization 头

    image.png

    3.限速(Rate limiting) 控制请求速率

    三、实例https://github.com/adelowo/rounder

    1.server.go

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func main() {
    
        mux := http.NewServeMux()
    
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            // This is here so we can actually see that the responses 
            // that have been cached don't actually get here
            fmt.Println("The request actually got here")
    
            w.Write([]byte("You got here"))
        })
    
        http.ListenAndServe(":8000", mux)
    }
    
    

    2.client.go

    package main
    
    import (
        "bufio"
        "bytes"
        "errors"
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        "net/http/httputil"
        "os"
        "os/signal"
        "strings"
        "sync"
        "syscall"
        "time"
    )
    
    func main() {
    
        cachedTransport := newTransport()
    
        //Create a custom client so we can make use of our RoundTripper
        //If you make use of http.Get(), the default http client located at http.DefaultClient is used instead
        //Since we have special needs, we have to make use of our own http.RoundTripper implementation
        client := &http.Client{
            Transport: cachedTransport,
            Timeout:   time.Second * 5,
        }
    
        //Time to clear the cache store so we can make request to the original server
        cacheClearTicker := time.NewTicker(time.Second * 5)
    
        //Make a new request every second
        //This would help demonstrate if the response is actually coming from the real server or from the cache
        reqTicker := time.NewTicker(time.Second * 1)
    
        terminateChannel := make(chan os.Signal, 1)
    
        signal.Notify(terminateChannel, syscall.SIGTERM, syscall.SIGHUP)
    
        req, err := http.NewRequest(http.MethodGet, "http://localhost:8000", strings.NewReader(""))
    
        if err != nil {
            log.Fatalf("An error occurred ... %v", err)
        }
    
        for {
            select {
            case <-cacheClearTicker.C:
                // Clear the cache so we can hit the original server
                cachedTransport.Clear()
    
            case <-terminateChannel:
                cacheClearTicker.Stop()
                reqTicker.Stop()
                return
    
            case <-reqTicker.C:
    
                resp, err := client.Do(req)
    
                if err != nil {
                    log.Printf("An error occurred.... %v", err)
                    continue
                }
    
                buf, err := ioutil.ReadAll(resp.Body)
    
                if err != nil {
                    log.Printf("An error occurred.... %v", err)
                    continue
                }
    
                fmt.Printf("The body of the response is \"%s\" \n\n", string(buf))
            }
        }
    }
    
    func cacheKey(r *http.Request) string {
        return r.URL.String()
    }
    
    type cacheTransport struct {
        data              map[string]string
        mu                sync.RWMutex
        originalTransport http.RoundTripper
    }
    
    func (c *cacheTransport) Set(r *http.Request, value string) {
        c.mu.Lock()
        defer c.mu.Unlock()
        fmt.Println("cache key:",cacheKey(r))
        fmt.Println("cache value:",value)
        c.data[cacheKey(r)] = value
    }
    
    func (c *cacheTransport) Get(r *http.Request) (string, error) {
        c.mu.RLock()
        defer c.mu.RUnlock()
    
        if val, ok := c.data[cacheKey(r)]; ok {
            return val, nil
        }
    
        return "", errors.New("key not found in cache")
    }
    
    // There be dragons!!!
    func (c *cacheTransport) RoundTrip(r *http.Request) (*http.Response, error) {
    
        // Check if we have the response cached..
        // If yes, we don't have to hit the server
        // We just return it as is from the cache store.
        if val, err := c.Get(r); err == nil {
            fmt.Println("Fetching the response from the cache")
            return cachedResponse([]byte(val), r)
        }
    
        // Ok, we don't have the response cached, the store was probably cleared.
        // Make the request to the server.
        resp, err := c.originalTransport.RoundTrip(r)
    
        if err != nil {
            return nil, err
        }
    
        // Get the body of the response so we can save it in the cache for the next request.
        buf, err := httputil.DumpResponse(resp, true)
    
        if err != nil {
            return nil, err
        }
    
        // Saving it to the cache store
        c.Set(r, string(buf))
    
        fmt.Println("Fetching the data from the real source")
        return resp, nil
    }
    
    func (c *cacheTransport) Clear() error {
        c.mu.Lock()
        defer c.mu.Unlock()
    
        c.data = make(map[string]string)
        return nil
    }
    
    func cachedResponse(b []byte, r *http.Request) (*http.Response, error) {
        buf := bytes.NewBuffer(b)
        return http.ReadResponse(bufio.NewReader(buf), r)
    }
    
    func newTransport() *cacheTransport {
        return &cacheTransport{
            data:              make(map[string]string),
            originalTransport: http.DefaultTransport,
        }
    }
    
    
    四、Go 标准库剖析 1(transport http 请求的承载者)

    相关文章

      网友评论

          本文标题:Golang http.RoundTripper

          本文链接:https://www.haomeiwen.com/subject/uhgwvqtx.html