深入分析 http.RoundTripper

1、RoundTripper 接口

 

// RoundTripper is an interface representing the ability to execute a
// single HTTP transaction, obtaining the Response for a given Request.
//
// A RoundTripper must be safe for concurrent use by multiple
// goroutines.
type RoundTripper interface {
   // RoundTrip executes a single HTTP transaction, returning
   // a Response for the provided Request.
   //
   // RoundTrip should not attempt to interpret the response. In
   // particular, RoundTrip must return err == nil if it obtained
   // a response, regardless of the response's HTTP status code.
   // A non-nil err should be reserved for failure to obtain a
   // response. Similarly, RoundTrip should not attempt to
   // handle higher-level protocol details such as redirects,
   // authentication, or cookies.
   //
   // RoundTrip should not modify the request, except for
   // consuming and closing the Request's Body. RoundTrip may
   // read fields of the request in a separate goroutine. Callers
   // should not mutate or reuse the request until the Response's
   // Body has been closed.
   //
   // RoundTrip must always close the body, including on errors,
   // but depending on the implementation may do so in a separate
   // goroutine even after RoundTrip returns. This means that
   // callers wanting to reuse the body for subsequent requests
   // must arrange to wait for the Close call before doing so.
   //
   // The Request's URL and Header fields must be initialized.
   RoundTrip(*Request) (*Response, error)
}

一句话概括:可以把RoundTripper看成是 http.Client 的中间件

2、场景

(1)缓存http responses。如果缓存中存在,则直接从缓存中取。如果缓存中不存在,则重新请求

(2)根据需要适当的设置HTTP headers

(3)Rate limiting

3、实例

使用HTTP.roundtripper的实现来缓存HTTP响应

(1)实现一个 HTTP Server

 

package main

import (
   "fmt"
   "net/http"
)

func main() {

   mux := http.NewServeMux()

   mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
      //This is here so wen can actually see what the responses that have been cached don't get here
      fmt.Println("The request actually got here")
      writer.Write([]byte("You got here"))
   })
   http.ListenAndServe(":8080", mux)
}

(2)新建一个http.Transport并且实现 http.RoundTripper接口,同时让其具有缓存功能

 

package cacheTransport

import (
   "bufio"
   "bytes"
   "errors"
   "fmt"
   "net"
   "net/http"
   "net/http/httputil"
   "sync"
   "time"
)

func cachKey(r *http.Request) string {
   return r.URL.String()
}

type CacheTransport struct {
   data              map[string]string
   mu                sync.RWMutex
   originalTransport http.RoundTripper
}

func NewCacheTransport() *CacheTransport {
   return &CacheTransport{
      data: make(map[string]string, 20),
      originalTransport: &http.Transport{
         DialContext: (&net.Dialer{
            Timeout:   2 * time.Second,
            KeepAlive: 30 * time.Second,
            DualStack: true,
         }).DialContext,
         ResponseHeaderTimeout: 5 * time.Second,
         MaxIdleConns:          100,
         IdleConnTimeout:       90 * time.Second,
         TLSHandshakeTimeout:   10 * time.Second,
      }}
}

func (c *CacheTransport) RoundTrip(r *http.Request) (*http.Response, error) {
   //Check if we have the respose 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)
   }

   // 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 int the cache for the next request.
   buf, err := httputil.DumpResponse(resp, true)
   if err != nil {
      return nil, err
   }
   //Saving it to the store
   c.Set(r, string(buf))

   fmt.Println("Fetching the data form the real source")
   return resp, nil
}

func cachedResponse(b []byte, r *http.Request) (*http.Response, error) {
   buf := bytes.NewBuffer(b)
   return http.ReadResponse(bufio.NewReader(buf), r)
}
func (c *CacheTransport) Set(r *http.Request, value string) {
   c.mu.Lock()
   defer c.mu.Unlock()
   c.data[cachKey(r)] = value
}

func (c *CacheTransport) Get(r *http.Request) (string, error) {
   c.mu.Lock()
   defer c.mu.Unlock()

   if val, ok := c.data[cachKey(r)]; ok {
      return val, nil
   }

   return "", errors.New("key not found in cache")
}

func (c *CacheTransport) Clear() error {
   c.mu.Lock()
   c.mu.Unlock()
   c.data = make(map[string]string)
   return nil
}

(3)实现客户端的引导程序main函数,同时具有一个timer来定时清理缓存

 

package main

import (
   "awesomeProject/interview/roundtripper/cacheTransport"
   "fmt"
   "io/ioutil"
   "log"
   "net/http"
   "os"
   "os/signal"
   "strings"
   "syscall"
   "time"
)

func main() {

   cacheTransport := cacheTransport.NewCacheTransport()

   // 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: cacheTransport,
      Timeout:   time.Second * 5,
   }

   // Time to clear the cache store so we can make requet to the original server rather than fetch from the cache store
   // This is to replicate real expiration of data in a cache store
   cacheClearTicker := time.NewTicker(time.Second * 5)

   // Make a new request every second
   // This would help demonstrate if the response if coming from the real server or 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:8080", strings.NewReader(""))
   if err != nil {
      panic("Whoops")
   }

   for {
      select {
      case <-cacheClearTicker.C:
         // Clear the cache so we can hit the original server
         cacheTransport.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 if \"%s\" \n\n", string(buf))
      }
   }
}

}

(4)服务端执行结果如下

 

客户端执行结果如下:

 

参考文献:https://lanre.wtf/blog/2017/07/24/roundtripper-go/

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamCatcher

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值