使用context包可以在您的应用程序中构建请求链。context.Context包含请求的截止时间、取消信号和请求的元数据。请求处理程序应该向其所使用的任何函数或方法传递上下文,以便这些函数或方法可以访问请求的元数据。在本文中,我们将探讨如何在Go中使用context实现请求授权。
什么是上下文?
您可以将上下文视为请求级别的存储库,可以在整个请求处理过程中存储请求范围的值。这些值可以是键/值对,其中键是字符串,值可以是任何类型。在处理请求期间,任何组件都可以检索上下文中的值。
使用context传递元数据
为了使用上下文,请从context包导入Context类型。在此示例中,我们创建一个新的上下文并将元数据key1设置为“value1”:
import (
"context"
"fmt"
)
func main() {
ctx := context.WithValue(context.Background(), "key1", "value1")
value := ctx.Value("key1")
fmt.Println(value) // value1
}
在这个示例中,我们将上下文与值“value1”一起设置,并将上下文存储在ctx变量中。然后我们可以通过使用ctx.Value("key1")来检索值。
使用context传递截止时间
除了元数据之外,上下文还可以用于存储请求的截止时间。例如,在以下示例中,我们创建一个由3个协程组成的链,每个协程都打印自己的名称,并在1秒后传递请求:
import (
"context"
"fmt"
"time"
)
func doTask(ctx context.Context, name string) {
select {
case <-ctx.Done():
fmt.Printf("%s was canceled\n", name)
return
case <-time.After(time.Second):
fmt.Printf("%s is done\n", name)
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
go doTask(ctx, "Task1")
go doTask(ctx, "Task2")
go doTask(ctx, "Task3")
time.Sleep(time.Second * 3)
}
在此示例中,我们使用WithTimeout方法创建了一个新的上下文。这个上下文将在2秒钟后过期,如果在这个时候没有调用cancel函数,它将取消所有正在进行的任务并终止应用程序。例如,如果我们运行这个程序,我们可以看到以下结果:
Task1 is done
Task2 was canceled
Task3 was canceled
在这个示例中,由于第一个任务完成得比其余的任务早,所以只有它打印出“is done”。另外两个任务被取消了,因为它们没有在截止时间之前完成。
授权请求
现在我们已经了解了上下文的工作方式,我们可以开始使用它来授权请求。在我们的示例中,我们将模拟一个需要授权的请求。为了保持简单,我们将模拟两个用户,“alice”和“bob”,他们需要执行某些任务。我们想要授予“alice”用户访问这些任务的权限,而“bob”用户则没有任何权限。
创建中间件
在本例中,我们将使用中间件来授权请求。中间件是一个函数,它接受http.Handler并返回一个新的http.Handler。它使用http.Handler来处理请求,并在处理请求之前或之后添加某些行为。
我们将创建一个authorizeMiddleware函数,它将接受一个上下文和一个http.Handler。它将检查上下文中的元数据以确定是否授权请求。如果请求被授权,它将调用http.Handler。否则,它将向用户返回401 Unauthorized响应。
func authorizeMiddleware(ctx context.Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userValue := ctx.Value("user")
if userValue == nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
user := userValue.(string)
if user != "alice" {
w.WriteHeader(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
在这个函数中,我们检查上下文中的"user"元数据。如果user是“alice”,我们调用next.ServeHTTP(),这应该是我们的请求处理程序。否则,我们返回401 Unauthorized响应。
创建请求处理程序
在本例中,我们将使用http.HandlerFunc创建请求处理程序。它将简单地响应请求,并在响应中包含“Hello, world!”消息。
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
}
将中间件添加到处理程序链中
我们已经创建了授权中间件和请求处理程序,现在我们需要将它们放在一起。我们将使用http.NewServeMux()创建一个新的ServeMux,然后使用http.Handle()和http.HandlerFunc()将处理程序函数添加到它中。
func main() {
mux := http.NewServeMux()
mux.Handle("/", authorizeMiddleware(context.WithValue(context.Background(), "user", "alice"), http.HandlerFunc(helloHandler)))
http.ListenAndServe(":8000", mux)
}
在这个示例中,我们使用context.WithValue()创建了一个具有"user"元数据的上下文。我们将这个上下文传递给authorizeMiddleware()函数,它返回一个新的http.Handler。我们使用http.HandlerFunc()将helloHandler()插入中间件链中。
测试代码
为了测试我们的代码,我们将使用curl发送HTTP GET请求:
$ curl http://localhost:8000/
Hello, world!
在这个示例中,请求被授权,因为上下文中的"user"元数据包含值“alice”。helloHandler()函数向客户端发送“Hello, world!”消息。
如果我们使用curl发送HTTP GET请求,并指定不正确的用户:
$ curl -u bob http://localhost:8000/
Unauthorized
在这个示例中,请求未被授权,因为上下文中的"user"元数据包含值“bob”,而不是“alice”。authorizeMiddleware()函数向客户端返回401 Unauthorized响应。
结论
在本文中,我们探讨了如何在Go中使用context实现请求授权。我们通过使用上下文中的元数据存储从而授权请求。我们还创建了一个中间件,它检查请求是否包含正确的用户数据。授权请求可以在各种场景下使用,可以保护您的应用程序免受恶意访问。