Golang跨服务传递Context值的方法及认证场景选型咨询
我正在学习Golang,遇到了Context相关的问题:我希望在Server1向Server2发起请求时传递Server1设置的Context值,但Server2的Context中无法获取该值。测试时用curl请求http://localhost:8080,Server1控制台输出“Send request to http://localhost:8082”,Server2控制台输出“request coming in”和<nil>。请问如何让Server2获取Server1设置的Context值?同时想了解这种方式是否适合传递认证类数据,还是Middleware模式更合适?
Server1 代码
package main import ( "context" "fmt" "net/http" "github.com/go-chi/chi/v5" ) func main() { r := chi.NewRouter() r.HandleFunc("/", hello) fmt.Println("Starting listening on port 8080...") http.ListenAndServe(":8080", r) } // Context's key. type Sample string var sampleKey Sample = "sample" func hello(w http.ResponseWriter, r *http.Request) { fmt.Println("Send request to http://localhost:8082") ctx := context.WithValue(context.Background(), sampleKey, "1234") req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8082", nil) if err != nil { fmt.Println("Error while sending request: ", err) } // Send request. var c http.Client c.Do(req) }
Server2 代码
package main import ( "fmt" "net/http" "github.com/go-chi/chi/v5" ) func main() { r := chi.NewRouter() r.HandleFunc("/", receive) fmt.Println("Start listening on port 8082...") http.ListenAndServe(":8082", r) } type Sample string var sampleKey Sample func receive(w http.ResponseWriter, r *http.Request) { fmt.Println("request coming in") fmt.Println(r.Context().Value(sampleKey)) }
一、跨服务传递Context值的问题解决
Golang的context.WithValue仅支持同一个进程内的调用链传递数据,Server1和Server2是两个独立进程,HTTP请求不会自动携带进程内的Context数据——这是你获取<nil>的核心原因。
要实现跨服务数据传递,需要把Context中的值通过HTTP请求的自定义Header传递,再在Server2中读取Header值(也可存入Context供后续使用)。修改代码如下:
修改后的Server1核心代码
func hello(w http.ResponseWriter, r *http.Request) { fmt.Println("Send request to http://localhost:8082") req, err := http.NewRequest(http.MethodGet, "http://localhost:8082", nil) if err != nil { fmt.Println("Error while sending request: ", err) return } // 将需要传递的值放入HTTP Header req.Header.Set("X-Sample-Data", "1234") var c http.Client c.Do(req) }
修改后的Server2核心代码
func receive(w http.ResponseWriter, r *http.Request) { fmt.Println("request coming in") // 从Header读取传递的值 val := r.Header.Get("X-Sample-Data") fmt.Println(val) // 输出 "1234" // 若需存入Context供后续逻辑使用 type Sample string var sampleKey Sample = "sample" ctx := context.WithValue(r.Context(), sampleKey, val) // 后续处理可直接使用ctx }
另外,你原Server2代码中sampleKey未赋值(默认是空字符串),即使进程内传递也无法获取值,但核心问题还是跨进程Context无法直接传递。
二、认证类数据的传递方式选择
不推荐用进程内Context跨服务传递认证数据
进程内Context的设计初衷是在同进程调用链中传递请求域数据(如超时信号、请求ID),跨服务场景完全无效,必须依赖HTTP Header、URL参数或请求体传递数据。
推荐使用Middleware模式处理认证
对于JWT Token、API Key这类认证数据,Middleware模式是更优选择:
- 逻辑统一:请求进入业务Handler前,Middleware统一从Header(如
Authorization)读取认证数据,完成合法性校验后,将解析后的用户信息存入Context,后续Handler可直接取用。 - 复用性强:所有需认证的接口都能复用同一个Middleware,避免重复编写认证逻辑。
- 安全性可控:Middleware可统一处理认证失败场景(如返回401状态码),避免逻辑分散。
示例:Server2的认证Middleware实现
func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") // 模拟Token校验逻辑 if token == "" || token != "valid-jwt-token" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // 将用户ID存入Context ctx := context.WithValue(r.Context(), "user_id", "1001") next.ServeHTTP(w, r.WithContext(ctx)) }) } // 路由中挂载Middleware func main() { r := chi.NewRouter() r.Use(AuthMiddleware) // 全局生效,也可针对特定路由组挂载 r.HandleFunc("/", receive) fmt.Println("Start listening on port 8082...") http.ListenAndServe(":8082", r) } func receive(w http.ResponseWriter, r *http.Request) { userID := r.Context().Value("user_id").(string) fmt.Println("Authenticated user ID:", userID) }
内容的提问来源于stack exchange,提问作者Jassy

