unrolled / render

Go package for easily rendering JSON, XML, binary data, and HTML templates responses.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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