2015-06-26 8 views
7

Ich bin relativ neu zu Golang und versuche, die beste Möglichkeit, dies idiomatisch zu tun herauszufinden.Kontext an Gorilla Mux übergeben - Go Idiome

Ich habe ein Array von Routen, die ich statisch definiere und an gorilla/mux übergebe. Ich wickle jede Handler-Funktion mit etwas ein, um die Anfrage zeitlich anzupassen und Paniken zu behandeln (hauptsächlich, damit ich verstehen konnte, wie die Umhüllung funktionierte).

Ich möchte, dass sie Zugriff auf einen "Kontext" haben können - eine Struktur, die ein-pro-HTTP-Server sein wird, der Dinge wie Datenbank-Handles, Config usw. haben könnte. Was ich nicht tue möchte eine statische globale Variable verwenden.

Die Art, wie ich es gerade mache, kann ich den Wrappern Zugriff auf die Kontextstruktur geben, aber ich kann nicht sehen, wie dies in den tatsächlichen Handler zu bekommen, wie es ein http.HandlerFunc sein will. Ich dachte, was ich tun könnte, ist http.HandlerFunc in eine Art meiner eigenen konvertieren, dass ein Empfänger für Context war (und tun in ähnlicher Weise für die Wrapper, aber (nach viel spielen etwa) konnte ich dann nicht Handler() bekommen dies zu akzeptieren.

kann ich nicht helfen, aber denke ich etwas offensichtlich hier bin fehlt. unten-Code.

package main 

import (
    "fmt" 
    "github.com/gorilla/mux" 
    "html" 
    "log" 
    "net/http" 
    "time" 
) 

type Route struct { 
    Name  string 
    Method  string 
    Pattern  string 
    HandlerFunc http.HandlerFunc 
} 

type Context struct { 
    route *Route 
    // imagine other stuff here, like database handles, config etc. 
} 

type Routes []Route 

var routes = Routes{ 
    Route{ 
     "Index", 
     "GET", 
     "/", 
     index, 
    }, 
    // imagine lots more routes here 
} 

func wrapLogger(inner http.Handler, context *Context) http.Handler { 
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 
     start := time.Now() 

     inner.ServeHTTP(w, r) 

     log.Printf(
      "%s\t%s\t%s\t%s", 
      r.Method, 
      r.RequestURI, 
      context.route.Name, 
      time.Since(start), 
     ) 
    }) 
} 

func wrapPanic(inner http.Handler, context *Context) http.Handler { 
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 
     defer func() { 
      if err := recover(); err != nil { 
       log.Printf("panic caught: %+v", err) 
       http.Error(w, http.StatusText(500), 500) 
      } 
     }() 

     inner.ServeHTTP(w, r) 
    }) 
} 

func newRouter() *mux.Router { 

    router := mux.NewRouter().StrictSlash(true) 
    for _, route := range routes { 
     // the context object is created here 
     context := Context { 
      &route, 
      // imagine more stuff here 
     } 
     router. 
      Methods(route.Method). 
      Path(route.Pattern). 
      Name(route.Name). 
      Handler(wrapLogger(wrapPanic(route.HandlerFunc, &context), &context)) 
    } 

    return router 
} 

func index(w http.ResponseWriter, r *http.Request) { 
    // I want this function to be able to have access to 'context' 
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) 
} 

func main() { 
    fmt.Print("Starting\n"); 
    router := newRouter() 
    log.Fatal(http.ListenAndServe("127.0.0.1:8080", router)) 
} 

hier ein Weg, es zu tun, aber es scheint ziemlich schrecklich. ich denke nicht helfen kann, aber es muss sei ein besserer Weg, es zu tun - vielleicht zur Unterklasse (?) http.Handler.

package main 

import (
    "fmt" 
    "github.com/gorilla/mux" 
    "html" 
    "log" 
    "net/http" 
    "time" 
) 

type Route struct { 
    Name  string 
    Method  string 
    Pattern  string 
    HandlerFunc ContextHandlerFunc 
} 

type Context struct { 
    route *Route 
    secret string 
} 

type ContextHandlerFunc func(c *Context, w http.ResponseWriter, r *http.Request) 

type Routes []Route 

var routes = Routes{ 
    Route{ 
     "Index", 
     "GET", 
     "/", 
     index, 
    }, 
} 

func wrapLogger(inner ContextHandlerFunc) ContextHandlerFunc { 
    return func(c *Context, w http.ResponseWriter, r *http.Request) { 
     start := time.Now() 

     inner(c, w, r) 

     log.Printf(
      "%s\t%s\t%s\t%s", 
      r.Method, 
      r.RequestURI, 
      c.route.Name, 
      time.Since(start), 
     ) 
    } 
} 

func wrapPanic(inner ContextHandlerFunc) ContextHandlerFunc { 
    return func(c *Context, w http.ResponseWriter, r *http.Request) { 
     defer func() { 
      if err := recover(); err != nil { 
       log.Printf("panic caught: %+v", err) 
       http.Error(w, http.StatusText(500), 500) 
      } 
     }() 

     inner(c, w, r) 
    } 
} 

func newRouter() *mux.Router { 

    router := mux.NewRouter().StrictSlash(true) 
    for _, route := range routes { 
     context := Context{ 
      &route, 
      "test", 
     } 
     router.Methods(route.Method). 
      Path(route.Pattern). 
      Name(route.Name). 
      HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 
      wrapLogger(wrapPanic(route.HandlerFunc))(&context, w, r) 
     }) 
    } 

    return router 
} 

func index(c *Context, w http.ResponseWriter, r *http.Request) { 
    fmt.Fprintf(w, "Hello, %q secret is %s\n", html.EscapeString(r.URL.Path), c.secret) 
} 

func main() { 
    fmt.Print("Starting\n") 
    router := newRouter() 
    log.Fatal(http.ListenAndServe("127.0.0.1:8080", router)) 
} 
+1

Werfen Sie einen Blick auf http://github.com/ Alexedwards/Stack - Es bietet eine schöne Zusammenfassung für das Stapeln von Middlewares (Handler) mit einem einfachen Weg zu pa ss Kontext durch die Kette. – Nadh

+0

Gorilla selbst hat ein Kontext-Paket. – freeformz

+1

@freeformz Das Context-Paket von Gorilla ist eher für den Anfragekontext als für den Programmkontext gedacht. Insbesondere wird es am Ende einer Anfrage automatisch gelöscht (wenn Sie gorilla/mux verwenden). –

Antwort

3

I Go lerne und sich in der Mitte eines nahezu identischen Problem, und das ist, wie ich damit beschäftigt haben:


Erstens, ich glaube, Sie ein wichtiges Detail übersehen: Es gibt keine globalen Variablen in Go. Die widest scope you can have for a variable ist Paketumfang. Die einzigen echten Globals in Go sind predeclared identifiers wie true und false (und Sie können diese nicht ändern oder Ihre eigenen erstellen).

Also ist es völlig in Ordnung, einen Variablenbereich auf package main festzulegen, um den Kontext für Ihr Programm zu halten. Da ich aus einem C/C++ - Hintergrund kam, brauchte ich etwas Zeit, um mich daran zu gewöhnen. Da die Variablen paketorientiert sind, leiden sie nicht unter the problems of global variables. Wenn etwas in einem anderen Paket eine solche Variable benötigt, müssen Sie es explizit übergeben.

Haben Sie keine Angst, Paketvariablen zu verwenden, wenn es sinnvoll ist. Dies kann Ihnen helfen, die Komplexität in Ihrem Programm zu reduzieren, und in vielen Fällen machen Sie Ihre benutzerdefinierten Handler viel einfacher (wobei der Aufruf http.HandlerFunc() und das Übergeben eines Abschlusses ausreichen).

Eine solche einfache Handler könnte wie folgt aussehen:

func simpleHandler(c Context, next http.Handler) http.Handler { 
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 
    // FIXME Do something with our context 
    next.ServeHTTP(w, r) 
    }) 
} 

und verwendet werden:

r = mux.NewRouter() 
http.Handle("/", simpleHandler(c, r)) 

Wenn sich Ihre Bedürfnisse komplexer sind, müssen Sie Ihre eigene implementieren http.Handler . Denken Sie daran, dass ein http.Handler nur eine Schnittstelle ist, die ServeHTTP(w http.ResponseWriter, r *http.Request) implementiert.

Dies ist nicht getestet, aber Sie sollte etwa 95% des Weges dorthin gelangen:

package main 

import (
    "net/http" 
) 

type complicatedHandler struct { 
    h http.Handler 
    opts ComplicatedOptions 
} 

type ComplicatedOptions struct { 
    // FIXME All of the variables you want to set for this handler 
} 

func (m complicatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 
    // FIXME Do stuff before serving page 

    // Call the next handler 
    m.h.ServeHTTP(w, r) 

    // FIXME Do stuff after serving page 
} 

func ComplicatedHandler(o ComplicatedOptions) func(http.Handler) http.Handler { 
    return func(h http.Handler) http.Handler { 
     return complicatedHandler{h, o} 
    } 
} 

es zu benutzen:

r := mux.NewRouter() 
// FIXME: Add routes to the mux 

opts := ComplicatedOptions{/* FIXME */} 
myHandler := ComplicatedHandler(opts) 

http.Handle("/", myHandler(r)) 

Für ein weiter entwickelten Handler Beispiel basicAuth in goji/httpauth sehen, aus dem dieses Beispiel wurde schamlos abgerissen.


Einige weitere Lektüre:

Verwandte Themen