How best to use render in negroni middleware?
chmac opened this issue · comments
First off, thanks for sharing the code, it's a really helpful module.
We're using negroni and implementing a couple of our own handlers, primarily for authentication. If the authentication call fails, we want to return an error in JSON as well as the status code. Any advice on the most idiomatic / elegant way to achieve this?
My first thought was to pass the render.Render
to my custom middleware when I instantiate it. That is working, but I'm not sure if it's the best approach.
I tried to implement something along the lines of mikebthun/negronicql but I couldn't get it to work. Here's what I tried:
package renderer
import (
"github.com/gorilla/context"
"gopkg.in/unrolled/render.v1"
"net/http"
)
type RenderMiddleware struct {
Render *render.Render
}
func New(render *render.Render) *RenderMiddleware {
return &RenderMiddleware{render}
}
func (render RenderMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// Attach the renderer
context.Set(r, "Render", render.Render)
// Call the next middleware handler
next(rw, r)
}
Then in my package main
I have:
render := render.New(render.Options{})
renderer := renderer.New(render)
n.Use(renderer)
Then I can successfully use render := context.Get(r, "Render")
later, but when I try to use render.JSON()
I get this:
render.JSON undefined (type interface {} has no field or method JSON)
It's getting pretty late here, so I'm probably missing something obvious, in which case, if I work it out tomorrow morning, I'll post the answer here and close the ticket. :-)
Arg, found it. I knew it was something simple and stupid. Instead of render := context.Get(r, "Render")
I need to use render := context.Get(req, "Render").(*render.Render)
. I knew it was simple. :-)
Glad you got it figured out!
Is this an advised solution? I've been trying to figure out the same thing, but it seems weird to attach an existing renderer on every single call to a middleware handler?
While the solution above works, I personally have taken a different approach in most of my apps. Just like you would normally create a global DB
variable somewhere and always refer to that, I create a Render
variable somewhere convenient and use that:
// Render is the global renderer for all views.
var Render *render.Render
// Then you can initialize it or add this to a setup method elsewhere.
func init() {
Render = render.New(render.Options{
// options live here
})
}
Awesome, thanks a bunch!
@unrolled Thanks for sharing your example, really helpful. Are there any multi threading considerations here? Is Render threadsafe? In your example, it would be possible for two simultaneous executions of Render.JSON()
, are there any variables which might cause issues there?
I'm super new to Go, so you probably know the idioms better than me. Is creating a single DB variable the idiomatic approach? I saw the negronicql package attaching the DB handler to the session, but that may be the exception rather than the norm. I'd be grateful for any opinion you want to share.
My very inexperienced and pretty crude reading of the render.go and engine.go code didn't find any potential thread conflicts, but I don't understand the paradigm nearly well enough to be anything like confident in my conclusion.
I'd guess if it's not possible to set any options after the Render instance has been created, then it's probably thread safe by default, but I'll wait for your reply @unrolled before jumping to any conclusions. :-)
A global render.Render
object is threadsafe. Once it is initialized, the package is basically static. So no worries regarding race conditions or simultaneous executions.
For the DB variable, I would yes it's idiomatic to create a single instance and refer to that from everywhere. Be sure to refer to the global variable rather than passing the pointer along though, passing the pointer could led to issues if something changes the global variable.
A good example of this can be found here: https://github.com/sourcegraph/thesrc/blob/master/datastore/db.go
Can anyone help me Plz!
I want to make a separate package for routers and put all of my routes into this package and access them in my main package, according to url request, i'm using mux for routing and net/http.
Here is my main.go from Main Package
package main
import (
"RESTMONGOMVC/controllers"
"log"
"net/http"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
)
var (
session *mgo.Session
collection *mgo.Collection
err error
)
func getSession() *mgo.Session {
// Connect to our local mongo
s, err := mgo.Dial("mongodb://localhost")
// Check if connection error, is mongo running?
if err != nil {
panic(err)
}
// Deliver session
return s
}
func main() {
var err error
r := mux.NewRouter()
uc := controllers.NewNoteController(getSession())
r.HandleFunc("/api/notes", uc.GetNotes).Methods("GET")
r.HandleFunc("/api/notes", uc.CreateNote).Methods("POST")
r.HandleFunc("/api/notes/{id}", uc.UpdateNote).Methods("PUT")
r.HandleFunc("/api/notes/{id}", uc.DeleteNote).Methods("DELETE")
http.Handle("/api/", r)
http.Handle("/", http.FileServer(http.Dir(".")))
log.Println("Starting Mongodb Session")
session, err = mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
collection = session.DB("notesdb").C("notes")
log.Println("Listening on 8080")
http.ListenAndServe(":8080", nil)
}
You can just export the *http.ServeMux
represented by your r
variable.
i.e.
package handler
// You could also pass in an *Env struct that embeds your mgo connection and other
// "application wide" services that your routes need to use.
func Routes(session *mgo.Session) *http.ServeMux {
r := mux.NewRouter()
uc := controllers.NewNoteController(getSession())
r.HandleFunc("/api/notes", uc.GetNotes).Methods("GET")
r.HandleFunc("/api/notes", uc.CreateNote).Methods("POST")
r.HandleFunc("/api/notes/{id}", uc.UpdateNote).Methods("PUT")
r.HandleFunc("/api/notes/{id}", uc.DeleteNote).Methods("DELETE")
http.Handle("/api/", r)
// Truncated for brevity
return r
}
... and in your main package:
package main
func main() {
r := handler.Routes(getSession())
// Pass your `http.ServeMux` to ListenAndServe
http.ListenAndServe(":8080", r)
}
Basic example but I hope that's clear. Further questions might be better off asked on https://groups.google.com/forum/?fromgroups#!forum/golang-nuts or http://stackoverflow.com/questions/tagged/go