深入 net/http:看一下 http.RoundTripper

本文深入探讨了Go语言中处理HTTP请求的Roundtripping概念,这是一种在发送请求和接收响应之间拦截和操作HTTP事务的方法。通过实现http.RoundTripper接口,可以实现如缓存响应、身份验证、速率限制等功能。文章通过一个具体的缓存HTTP响应的示例,展示了如何使用RoundTripper来提高应用程序的效率和性能。
摘要由CSDN通过智能技术生成

我已经写了很多关于HTTP的文章。这篇博文只是另一篇文章,它讨论了 Go 处理方式中的另一个有趣概念,HTTP以及它如何使与 HTTP 相关的东西变得更加有趣。

在这篇文章中,我将介绍Round tripping是什么,它的适用用例和一个显示它的应用程序的小演示。

我想谈的这个概念被称为Round trippinggodoc描述它执行单个 HTTP 事务的能力,获得给定 Request 的 Response。基本上,这意味着能够了解发出HTTP请求和接收响应之间发生的情况。用外行的话来说,它就像中间件,但对于http.Client. 我这样说是因为往返发生在请求实际发送之前。

尽管可以在该RoundTrip方法中执行任何操作(例如 HTTP 处理程序的中间件),但建议您不要检查响应、返回错误(nil 或非 nil)并且不应该执行用户之类的操作身份验证(或 cookie 处理)..

因为http.RoundTripper是一个接口。要获得此功能,您所要做的就是实施RoundTrip

type SomeClient struct {}

func (s *SomeClient) RoundTrip(r *http.Request)(*Response, error) {
	//Something comes here...Maybe
}

这只是与 stdlib 中的其他方法接口保持一致。小而简洁。

用例

  • 缓存 http 响应。例如,您的网络应用程序必须连接到 Github 的 API 以获取内容(其中之一是趋势回购)。在现实生活中,这种情况经常发生变化,但我们假设他们每 30 分钟重建一次趋势板,并且您的应用程序拥有大量用户。您显然不想每次都点击 api 来请求趋势排行榜因为它在30 分钟的窗口中总是相同的,并且考虑到 API 调用是有速率限制的,并且由于您的应用程序的高使用率,您几乎总是达到/超过限制。

    一个解决方案是使用http.RoundTripper. 您可以http.Client使用执行以下操作的 RoundTripper 配置您的:

    • 缓存商店有这个项目吗?

      • 不要提出HTTP要求。
      • 通过将缓存中的数据读取到响应正文中来返回新的响应。
    • 缓存存储没有此项(可能是因为缓存每 31 分钟失效一次)

      • HTTPapi 发出请求。
      • 缓存从 api 接收到的数据。

您不必为此使用 RoundTripper,因为(在处理程序内)您可以在发出HTTP 请求之前检查缓存中是否存在某个项目。但是通过 RoundTripper 实现,您可能会正确分配职责[0]

  • 根据需要向请求添加适当的(授权)标头...... 一个很容易想到的例子是google/go-github,Github 的 api 的 Golang 客户端。Github 的 api 的某些部分需要对请求进行身份验证,有些则不需要。默认情况下,该库不处理身份验证,它使用默认的 HTTP 客户端,如果您需要能够访问 api 的经过身份验证的部分,您可以携带自己的 HTTP 客户端,例如oauth2受保护的端点。那么如何这个问题往返,有这个ghinstallation允许你使用 go-github 验证 Github 应用程序。如果您查看它的代码库,它所做的只是提供一个http.Client实现http.RoundTripper. 之后它在RoundTrip方法。

  • 速率限制。这与上面的非常相似,也许您有一个存储桶,用于保存最近建立的连接数。您检查您是否仍处于可接受的 API 状态,并决定是否应该发出请求、停止发出请求或安排它在将来运行。

  • 不管你有什么……也许没有

真实世界的使用

我们将HTTP通过http.RoundTripper. 我们将创建一个只响应一个路由的服务器,然后创建一个连接到该服务器的客户端包。客户端将利用它自己的实现,http.Client因此我们可以提供我们自己的 RoundTripper,因为我们正在尝试缓存响应。

所以这就是它的样子,

  • 客户端向服务器发出请求。
    • 如果该 url 的响应存在于缓存存储中?
      • 不要调用服务器。
      • 从商店取货。
      • 将其写入响应并直接返回。
    • 如果缓存存储中不存在该 url 的响应
      • 向服务器发出请求。
      • 将响应的主体写入缓存存储。
      • 返回响应。

这已经放在github上了。


$ mkdir client server

我们将首先构建服务器,因为它的实现非常简单

import (
	"fmt"
	"net/http"
)

func main() {

	// server/main.go

	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 get here
		fmt.Println("The request actually got here")

		w.Write([]byte("You got here"))
	})

	http.ListenAndServe(":8000", mux)
}

然后我们将构建客户端包。这是最有趣的部分,虽然它很长(130 多个 LOC),但应该相对容易理解。我强烈建议您前往github存储库。

首先,我们需要一个缓存存储。由于这是一个最小的项目,字典/地图可以帮助我们尽快离开。我们将创建一个http.Transport实现http.RoundTripper但也是缓存存储的对象。

但在现实生活中,您会希望将它们彼此分开。

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()
	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")
}

// Here is the main functionality
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 main() {

	//client/main/go

	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 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 is 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:8000", strings.NewReader(""))

	if err != nil {
		panic("Whoops")
	}

	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))
		}
	}
}

为了测试这一点,我们必须构建两个程序 -client/main.goserver/main.go./client使用和在各自的目录中运行它们./server。你应该得到这样的东西

并观察打印到终端的内容,您会注意到有些地方说“从缓存中获取”,而有些地方说“从服务器中获取”。最有趣的部分是如果您查看 的实现server/main.go,我们有一个fmt.Println只有在调用服务器时才会执行,您会注意到只有在客户端打印“从服务器获取”时才会看到它。

另一件需要注意的事情是,无论我们是否访问服务器,响应的主体都会保持不变。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值