2

My basic main setup:

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())

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

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

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("DomainName", domain)

Prints: DomainName <nil>

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.

borislemke
  • 8,446
  • 5
  • 41
  • 54

2 Answers2

1

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

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

// Replaced with:

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

AND

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

// Replaced with:

domain := req.Context().Value(helperKeys.DomainName).(string)
borislemke
  • 8,446
  • 5
  • 41
  • 54
  • Please use the longer version of type conversion `(domain, ok := req.Context().Value(...)` It will save you a lot of headaches if there is an issue like the value never being set. I would also use custom getters/setters for context values and unexported keys. All of which are safer in practice and not much more code. – joncalhoun Feb 14 '17 at 18:14
0

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, "Created a user...")
}

func main() {
  db := ...
  s := Something{db}
  http.HandleFunc("/", 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 = "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 {
    // Log this issue
    return nil
  }
  return user
}
joncalhoun
  • 1,498
  • 1
  • 17
  • 22
  • Hey thanks for the heads up, but I'm not actually storing the DB, I'm just storing a copy of the connection session to the database. I connect to the database in the main function and only store a copy of the session in each request's context – borislemke Feb 14 '17 at 18:48
  • That is still generally frowned upon - a copy of the DB connection isn't specific to the request at all and could just as easily be accessed via the first approach I showed here, which is much safer in the long term. What *might* be more kosher is storing say a transaction created from the DB connection, and that transaction will only live as long as the request is alive. That said, I get the desire to do this to speed things up. I just figured I'd let you know that there are better ways to handle this as your app grows that are easier to maintain and debug. – joncalhoun Feb 14 '17 at 19:00