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/