
本文介绍在 go web 开发中,如何将 api 请求获取的结构化数据(如 json 响应)安全、高效地暴露给其他处理函数,避免重复请求并支持多 handler 共享,重点讲解基于全局变量、sync.map 和 context 的三种实践方案。
本文介绍在 go web 开发中,如何将 api 请求获取的结构化数据(如 json 响应)安全、高效地暴露给其他处理函数,避免重复请求并支持多 handler 共享,重点讲解基于全局变量、sync.map 和 context 的三种实践方案。
在 Go 中,HTTP 处理函数(如 RequestTopMovies)本质上是 http.Handler 类型,由 HTTP 服务器自动调用,其作用域受限于单次请求生命周期。若希望将解析后的响应数据(例如 Payload 结构体)供其他 handler 或业务逻辑复用,不能依赖局部变量或函数返回值——因为 handler 无显式返回、无法直接赋值给外部变量。以下是三种推荐方案,按安全性与适用场景递进说明:
✅ 方案一:使用 sync.Map 实现线程安全的全局缓存(推荐用于简单共享)
sync.Map 是 Go 标准库提供的并发安全映射,适合在多个 goroutine(如不同 HTTP 请求)间共享只读或低频更新的数据:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
)
type Payload struct {
Results []struct {
Overview string `json:"overview"`
} `json:"results"`
}
// 全局并发安全缓存(key 可为 API 名称、时间戳或业务标识)
var cache = sync.Map{} // key: string, value: Payload
func RequestTopMovies(w http.ResponseWriter, r *http.Request) {
url := "https://api.example.com/movies/top"
res, err := http.Get(url)
if err != nil {
http.Error(w, "Failed to fetch data", http.StatusInternalServerError)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
http.Error(w, "Failed to read response", http.StatusInternalServerError)
return
}
var p Payload
if err := json.Unmarshal(body, &p); err != nil {
http.Error(w, "Invalid JSON response", http.StatusBadRequest)
return
}
// 安全写入缓存(key 示例:"top_movies")
cache.Store("top_movies", p)
fmt.Fprintln(w, "Data cached successfully")
}
func ShowCachedMovies(w http.ResponseWriter, r *http.Request) {
if val, ok := cache.Load("top_movies"); ok {
p := val.(Payload)
for _, movie := range p.Results {
fmt.Fprintln(w, movie.Overview)
}
} else {
http.Error(w, "No cached data available", http.StatusNotFound)
}
}
⚠️ 注意事项:
- 避免使用 map[string]Payload{} 等非并发安全类型,否则在高并发下会 panic;
- sync.Map 适用于读多写少场景;若需频繁更新+复杂查询,建议改用数据库或 Redis。
✅ 方案二:通过 context.Context 传递请求级数据(推荐用于单次请求链路)
若数据仅需在同一次 HTTP 请求的中间件/子处理逻辑中流转(如鉴权 → 日志 → 业务处理),应优先使用 context.WithValue:
const payloadKey = "payload"
func WithPayload(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 模拟请求数据获取(生产中可异步或缓存)
p := Payload{Results: []struct{ Overview string }{{"A sci-fi epic..."}}}
ctx := context.WithValue(r.Context(), payloadKey, p)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func BusinessHandler(w http.ResponseWriter, r *http.Request) {
if p, ok := r.Context().Value(payloadKey).(Payload); ok {
fmt.Fprintln(w, "From context:", p.Results[0].Overview)
}
}
✅ 优势:无状态、解耦、符合 Go 的 context 设计哲学;
❌ 局限:仅限单请求生命周期,无法跨请求共享。
✅ 方案三:封装为可注入的服务(推荐用于大型项目)
将 API 调用抽象为独立服务,通过依赖注入(如构造函数传参)提供给各 handler:
type MovieService struct {
cache sync.Map
}
func (s *MovieService) FetchTopMovies() (Payload, error) {
if val, ok := s.cache.Load("top_movies"); ok {
return val.(Payload), nil
}
// 实际 HTTP 请求逻辑(省略错误处理)
res, _ := http.Get("https://api.example.com/movies/top")
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
var p Payload
json.Unmarshal(body, &p)
s.cache.Store("top_movies", p)
return p, nil
}
// 在 main() 中初始化并注入
func main() {
svc := &MovieService{}
http.HandleFunc("/movies/top", func(w http.ResponseWriter, r *http.Request) {
p, _ := svc.FetchTopMovies()
for _, m := range p.Results {
fmt.Fprintln(w, m.Overview)
}
})
http.ListenAndServe(":8080", nil)
}
总结
- 临时共享 + 快速验证 → 用 sync.Map;
- 单请求内传递 → 用 context.Context;
-
长期维护 + 可测试性要求高 → 封装为服务并依赖注入。
永远避免 panic(err) 在 Web handler 中——应统一返回 HTTP 错误;同时注意缓存过期、并发竞争与内存泄漏风险。











