Pasar contexto a métodos de interfaz
Algo inspirado porEste artículo la semana pasada, estoy jugando con la refactorización de una aplicación que tengo para pasar más explícitamente el contexto (grupos de bases de datos, almacenes de sesiones, etc.) a mis controladores.
Sin embargo, un problema que tengo es que sin un mapa de plantillas globales, elServeHTTP
método en mi tipo de controlador personalizado (como para satisfacerhttp.Handler
) ya no puede acceder al mapa para representar una plantilla.
Necesito retener lo globaltemplates
variable, o redefinir mi tipo de controlador personalizado como una estructura.
¿Hay una mejor manera de lograr esto?
func.go
package main
import (
"fmt"
"log"
"net/http"
"html/template"
"github.com/gorilla/sessions"
"github.com/jmoiron/sqlx"
"github.com/zenazn/goji/graceful"
"github.com/zenazn/goji/web"
)
var templates map[string]*template.Template
type appContext struct {
db *sqlx.DB
store *sessions.CookieStore
}
type appHandler func(w http.ResponseWriter, r *http.Request) (int, error)
func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// templates must be global for us to use it here
status, err := ah(w, r)
if err != nil {
log.Printf("HTTP %d: %q", status, err)
switch status {
case http.StatusNotFound:
// Would actually render a "http_404.tmpl" here...
http.NotFound(w, r)
case http.StatusInternalServerError:
// Would actually render a "http_500.tmpl" here
// (as above)
http.Error(w, http.StatusText(status), status)
default:
// Would actually render a "http_error.tmpl" here
// (as above)
http.Error(w, http.StatusText(status), status)
}
}
}
func main() {
// Both are 'nil' just for this example
context := &appContext{db: nil, store: nil}
r := web.New()
r.Get("/", appHandler(context.IndexHandler))
graceful.ListenAndServe(":8000", r)
}
func (app *appContext) IndexHandler(w http.ResponseWriter, r *http.Request) (int, error) {
fmt.Fprintf(w, "db is %q and store is %q", app.db, app.store)
return 200, nil
}
struct.go
package main
import (
"fmt"
"log"
"net/http"
"html/template"
"github.com/gorilla/sessions"
"github.com/jmoiron/sqlx"
"github.com/zenazn/goji/graceful"
"github.com/zenazn/goji/web"
)
type appContext struct {
db *sqlx.DB
store *sessions.CookieStore
templates map[string]*template.Template
}
// We need to define our custom handler type as a struct
type appHandler struct {
handler func(w http.ResponseWriter, r *http.Request) (int, error)
c *appContext
}
func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
status, err := ah.handler(w, r)
if err != nil {
log.Printf("HTTP %d: %q", status, err)
switch status {
case http.StatusNotFound:
// Would actually render a "http_404.tmpl" here...
http.NotFound(w, r)
case http.StatusInternalServerError:
// Would actually render a "http_500.tmpl" here
// (as above)
http.Error(w, http.StatusText(status), status)
default:
// Would actually render a "http_error.tmpl" here
// (as above)
http.Error(w, http.StatusText(status), status)
}
}
}
func main() {
// Both are 'nil' just for this example
context := &appContext{db: nil, store: nil}
r := web.New()
// A little ugly, but it works.
r.Get("/", appHandler{context.IndexHandler, context})
graceful.ListenAndServe(":8000", r)
}
func (app *appContext) IndexHandler(w http.ResponseWriter, r *http.Request) (int, error) {
fmt.Fprintf(w, "db is %q and store is %q", app.db, app.store)
return 200, nil
}
¿Hay alguna forma más limpia de pasar elcontext
instancia aServeHTTP
?
Tenga en cuenta quego build -gcflags=-m
muestra que ninguna de las opciones parece ser peor en equipos de asignación de almacenamiento dinámico: el&appContext
literal escapa al montón (como se esperaba) en ambos casos, aunque mi interpretación es que la opción basada en estructura pasa un segundo puntero (acontext
) en cada solicitud:corrígeme si me equivoco aquí ya que me encantaría entenderlo mejor.
No estoy totalmente convencido de que los globales sean malos en el paquete principal (es decir, no una lib), siempre que sean seguros de usar de esa manera (solo lectura / mutexes / un grupo), pero me gusta la claridad que tiene que pasar explícitamente el contexto.