Передача контекста в методы интерфейса
Несколько вдохновленЭта статья На прошлой неделе я занимаюсь рефакторингом приложения, которое мне нужно более явно передать контексту (пулы БД, хранилища сессий и т. д.) моим обработчикам.
Однако у меня есть одна проблема: без карты глобальных шаблоновServeHTTP
метод на мой тип пользовательского обработчика (как удовлетворитьhttp.Handler
) больше не может получить доступ к карте для визуализации шаблона.
Мне нужно либо сохранить глобальныйtemplates
переменная, или переопределите мой пользовательский тип обработчика как структуру.
Есть ли лучший способ добиться этого?
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
}
Есть ли более чистый способ пройтиcontext
экземпляр дляServeHTTP
?
Обратите внимание, чтоgo build -gcflags=-m
показывает, что ни один из вариантов не выглядит хуже в командах распределения кучи:&appContext
в обоих случаях литерал сбрасывается в кучу (как и ожидалось), хотя моя интерпретация заключается в том, что опция на основе структуры передает второй указатель (наcontext
) по каждому запросупоправьте меня если я не прав как я хотел бы получить лучшее понимание этого.
Я не совсем уверен, что глобальные переменные плохи в основном пакете (то есть не в lib), при условии, что они безопасны для использования таким образом (только для чтения / mutexes / a pool), но мне нравится ясность, необходимая для явной передачи контекста.