在嵌套的Gorilla子路由器中,Negroni中间件中设置的请求上下文会丢失。

huangapple go评论115阅读模式
英文:

Request context set in negroni middleware is lost in nested gorilla Subrouter

问题

我的基本main设置:

muxRouter := mux.NewRouter()

v1Router.Router(muxRouter.PathPrefix("/v1").Subrouter())

http.Handle("/", muxRouter)


n := negroni.Classic()
n.Use(negroni.HandlerFunc(apiRouter.Middleware))
n.UseHandler(muxRouter)

s := &http.Server{
    Addr:           ":6060",
    Handler:        n,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

apiRouter.Middleware中,我设置了以下上下文:

context.Set(req, helperKeys.DomainName, "some-value")

然而,在v1Router.Router中的某个handlerFunc中,当尝试Get上下文的值时,结果为nil

domain := context.Get(req, helperKeys.DomainName)
fmt.Println("DomainName", domain)

输出:DomainName <nil>

我知道Set方法是正确的,因为在apiRouter.Middleware中设置后立即获取值将返回正确的字符串值。

英文:

My basic main setup:

muxRouter := mux.NewRouter()

v1Router.Router(muxRouter.PathPrefix(&quot;/v1&quot;).Subrouter())

http.Handle(&quot;/&quot;, muxRouter)


n := negroni.Classic()
n.Use(negroni.HandlerFunc(apiRouter.Middleware))
n.UseHandler(muxRouter)

s := &amp;http.Server{
    Addr:           &quot;:6060&quot;,
    Handler:        n,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 &lt;&lt; 20,
}
log.Fatal(s.ListenAndServe())

Inside the apiRouter.Middleware I have set the following context:

context.Set(req, helperKeys.DomainName, &quot;some-value&quot;)

However, in some handlerFunc within v1Router.Router when trying to Get the context's value, the result is nil:

domain := context.Get(req, helperKeys.DomainName)
fmt.Println(&quot;DomainName&quot;, domain)

Prints: DomainName &lt;nil&gt;

I know that the Set method is correct as getting the value immediately after setting it in the apiRouter.Middleware will return the correct string value.

答案1

得分: 1

我最终使用了Go 1.7内置的Context

context.Set(req, helperKeys.DomainName, "some-value")

// 替换为:

ctx := req.Context()
ctx = context.WithValue(ctx, helperKeys.DomainName, "some-value")
req = req.WithContext(ctx)

并且

domain := context.Get(req, helperKeys.DomainName)

// 替换为:

domain := req.Context().Value(helperKeys.DomainName).(string)
英文:

I ended up using Go 1.7's built in Context:

context.Set(req, helperKeys.DomainName, &quot;some-value&quot;)

// Replaced with:

ctx := req.Context()
ctx = context.WithValue(ctx, helperKeys.DomainName, &quot;some-value&quot;)
req = req.WithContext(ctx)

AND

domain := context.Get(req, helperKeys.DomainName)

// Replaced with:

domain := req.Context().Value(helperKeys.DomainName).(string)

答案2

得分: 0

根据你的回答,看起来你正在尝试在上下文中存储一个数据库。我不建议这样做。相反,你可以尝试像这样做:

type Something struct {
  DB *sql.DB // 或其他数据库对象
}

func (s *Something) CreateUser(w http.ResponseWriter, r *http.Request) {
  // 使用 s.DB 来访问数据库
  fmt.Fprintln(w, "创建了一个用户...")
}

func main() {
  db := ...
  s := Something{db}
  http.HandleFunc("/", s.CreateUser)
  // ... 其他部分与正常情况下相似
}

这样可以让处理程序在不必每次都设置上下文的情况下访问数据库。上下文值应该保留给那些在运行时无法设置的事物。例如,特定于该网络请求的请求ID。那些超出请求生命周期的事物通常不属于这个范畴,而你的数据库连接将超出请求的生命周期。

如果你确实需要上下文值,你应该:

  1. 使用具有类型的getter和setter
  2. “包应该将键定义为未导出类型,以避免冲突。”- 来自Go源代码

下面是一个示例,我在这篇博文中更详细地讨论了上下文值的一般情况:

type userCtxKeyType string

const userCtxKey userCtxKeyType = "user"

func WithUser(ctx context.Context, user *User) context.Context {  
  return context.WithValue(ctx, userCtxKey, user)
}

func GetUser(ctx context.Context) *User {  
  user, ok := ctx.Value(userCtxKey).(*User)
  if !ok {
    // 记录此问题
    return nil
  }
  return user
}

希望对你有所帮助!

英文:

Based on your answer, it looks like you are trying to store a database in the context. I wouldn't suggest doing that. Instead try something like this:

type Something struct {
  DB *sql.DB // or some other DB object
}

func (s *Something) CreateUser(w http.ResponseWriter, r *http.Request) {
  // use s.DB to access the database
  fmt.Fprintln(w, &quot;Created a user...&quot;)
}

func main() {
  db := ...
  s := Something{db}
  http.HandleFunc(&quot;/&quot;, s.CreateUser)
  // ... everything else is pretty much like normal.
}

This gives your handlers access to the database while not having to set it on the context every single time. Context values should be reserved for things that you can't possibly set until runtime. For example, a request ID that is specific to that web request. Things that outlive the request don't typically fall into this category, and your DB connection will outlive the request.

If you do actually need context values, you should:

  1. Use getters and setters that are typed
  2. "packages should define keys as an unexported type to avoid collisions." - From the Go source code

An example of this is shown below, and I talk more about context values in general in this blog post:

type userCtxKeyType string

const userCtxKey userCtxKeyType = &quot;user&quot;

func WithUser(ctx context.Context, user *User) context.Context {  
  return context.WithValue(ctx, userCtxKey, user)
}

func GetUser(ctx context.Context) *User {  
  user, ok := ctx.Value(userCtxKey).(*User)
  if !ok {
    // Log this issue
    return nil
  }
  return user
}

huangapple
  • 本文由 发表于 2017年2月14日 23:09:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/42229594.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定