在Golang中克隆HTTP请求上下文的实例

435 阅读2分钟

在这个例子中,我们将克隆HTTP请求上下文及其所有的上下文键/值对。然而,重要的一点是,我们将放弃取消或最后期限的传播。原因是,当响应被返回或上下文因任何原因被取消时,我们不希望我们的上下文被终止并过早地破坏我们的应用程序。

这里有两个选项。第一个是你在一个全新的上下文上手动设置所有的键/值对,并忽略HTTP请求上下文。然而,如果你不知道key/value,那么这就没有用了。第二种选择是我们上面所描述的,所以我们要分离并克隆现有的HTTP请求上下文。

有一点你需要注意的是。如果HTTP请求上下文与你需要上下文的后台任务无关,只需创建一个全新的上下文。不要克隆HTTP请求上下文。

我们的例子很简单。我们将有两个中间件,我们在HTTP请求上下文中设置一些键/值对。我们还将在其上添加超时/截止日期。然后我们将打印出原始和分离/克隆的上下文的样子。

结构

├── context
│   └── detached.go
├── main.go
└── middleware
    ├── one.go
    └── two.go

文件

detached.go

package context

import (
	"context"
	"time"
)

type detached struct {
	ctx context.Context
}

func (detached) Deadline() (time.Time, bool) {
	return time.Time{}, false
}

func (detached) Done() <-chan struct{} {
	return nil
}

func (detached) Err() error {
	return nil
}

func (d detached) Value(key interface{}) interface{} {
	return d.ctx.Value(key)
}

func Detach(ctx context.Context) context.Context {
	return detached{ctx: ctx}
}

one.go

不要像我下面做的那样设置上下文的键/值,正确使用上下文键:

package middleware

import (
	"context"
	"net/http"
	"time"
)

func One(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := context.WithValue(r.Context(), "one-key", "one-value")

		// This will set a deadline on the context.
		ctx, cancel := context.WithTimeout(ctx, time.Millisecond*1)
		_ = cancel

		h.ServeHTTP(w, r.WithContext(ctx))
	})
}

two.go

不要像我在下面做的那样设置上下文键/值,正确使用上下文键:

package middleware

import (
	"context"
	"net/http"
	"time"
)

func Two(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := context.WithValue(r.Context(), "two-key", "two-value")

		// This will set a deadline on the context.
		ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Millisecond*time.Duration(1)))
		_ = cancel

		h.ServeHTTP(w, r.WithContext(ctx))
	})
}

main.go

package main

import (
	"fmt"
	"log"
	"net/http"

	"sport/context"
	"sport/middleware"
)

func main() {
	log.Println("sport running...")

	handler := http.NewServeMux()
	handler.HandleFunc("/", home)

	log.Fatalln(http.ListenAndServe(":8181", middleware.One(middleware.Two(handler))))
}

func home(w http.ResponseWriter, r *http.Request) {
	log.Println("home")

	// Detached context.
	detachedCtx := context.Detach(r.Context())
	detachedDeadline, ok := detachedCtx.Deadline()
	fmt.Println("DETACHED ---")
	fmt.Println("Deadline():", detachedDeadline, ok)
	fmt.Println("Err():", r.Context().Err())
	log.Println(detachedCtx.Value("one-key"))
	log.Println(detachedCtx.Value("two-key"))

	// Original context.
	originalCtx := r.Context()
	originalDeadline, ok := originalCtx.Deadline()
	fmt.Println("ORIGINAL ---")
	fmt.Println("Deadline():", originalDeadline, ok)
	fmt.Println("Err():", r.Context().Err())
	log.Println(originalCtx.Value("one-key"))
	log.Println(originalCtx.Value("two-key"))
}

测试

当你调用http://localhost:8181 ,终端的输出应该如下图所示。正如你所看到的,我们分离/克隆的上下文丢弃了超时/截止信息,所以它不会再被取消了:

DETACHED ---
Deadline(): 0001-01-01 00:00:00 +0000 UTC false
Err(): 
2020/09/15 12:42:14 one-value
2020/09/15 12:42:14 two-value
ORIGINAL ---
Deadline(): 2020-09-15 12:42:14.746067 +0100 BST m=+5.217963805 true
Err(): 
2020/09/15 12:42:14 one-value
2020/09/15 12:42:14 two-value