Kontext an Interface-Methoden übergeben

Etwas inspiriert vonDieser Beitrag Letzte Woche spiele ich mit dem Refactoring einer Anwendung, bei der ich den Kontext (DB-Pools, Sitzungsspeicher usw.) expliziter an meine Handler übergeben muss.

Ein Problem, das ich habe, ist jedoch, dass ohne eine globale Vorlagenzuordnung dieServeHTTP Methode auf meinem benutzerdefinierten Handlertyp (um zu befriedigenhttp.Handler) kann nicht mehr auf die Karte zugreifen, um eine Vorlage zu rendern.

Ich muss entweder das Globale behaltentemplates Variable, oder definieren Sie meinen benutzerdefinierten Handlertyp als Struktur neu.

Gibt es einen besseren Weg, dies zu erreichen?

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
}

Gibt es eine sauberere Möglichkeit, das zu passieren?context Instanz zuServeHTTP?

Beachten Sie, dassgo build -gcflags=-m zeigt, dass in Heap-Allokationsteams keine der beiden Optionen schlechter zu sein scheint:&appContext In beiden Fällen wird das Literal (wie erwartet) auf den Heap geschrieben, obwohl meine Interpretation lautet, dass die strukturbasierte Option einen zweiten Zeiger (an) übergibtcontext) bei jeder Anfrage—korrigiere mich wenn ich mich hier irre Ich würde gerne ein besseres Verständnis dafür bekommen.

Ich bin nicht ganz davon überzeugt, dass Globals im Paket main schlecht sind (d. H. Keine lib), vorausgesetzt, sie können auf diese Weise sicher verwendet werden (read only / mutexes / a pool), aber ich mag Klarheit, wenn der Kontext explizit übergeben wird.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage