2015-06-26 13 views
7

Sono ragionevolmente nuovo in Golang e sto cercando di risolvere il modo migliore per farlo in modo idiomatico.Passare il contesto a gorilla mux - go idiomi

Ho una serie di percorsi che sto definendo staticamente e passando a gorilla/mux. Sto facendo il wrapping di ogni funzione del gestore con qualcosa per volta la richiesta e gestisco il panico (principalmente così ho potuto capire come funzionava il wrapping).

Voglio che ognuno di essi sia in grado di accedere a un "contesto" - una struttura che sarà un-per-http-server, che potrebbe avere cose come handle di database, configurazione ecc. Cosa non faccio voglio fare è usare una variabile globale statica.

Nel modo in cui lo sto facendo, posso dare ai wrapper l'accesso alla struttura di contesto, ma non riesco a vedere come ottenere questo nel gestore vero e proprio, poiché vuole che sia uno http.HandlerFunc. Ho pensato che avrei potuto fare è convertire http.HandlerFunc in un tipo del mio proprio che era un ricevitore per Context (e fare allo stesso modo per gli involucri, ma (dopo molto giocare su) non ho potuto quindi ottenere Handler() di accettare questo.

non posso fare a meno di pensare che mi manca qualcosa di ovvio qui. codice di seguito.

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)) 
} 

Ecco un modo per farlo, ma sembra abbastanza orribile. non posso fare a meno di pensare che ci deve essere un modo migliore per farlo - forse per sottoclasse (?) 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

Date un'occhiata a http://github.com/ alexedwards/stack - Fornisce un bel riassunto per l'accatastamento di middleware (handler) con un modo semplice per pa contesto ss attraverso la catena. – Nadh

+0

Gorilla stesso ha un pacchetto di contesto. – freeformz

+1

@freeformz Il pacchetto di contesto di Gorilla è destinato al contesto della richiesta, piuttosto che al contesto del programma. In particolare, viene cancellato automaticamente alla fine di una richiesta (se si utilizza gorilla/mux). –

risposta

3

Sto imparando Go e attualmente al centro di un problema quasi identico, e questo è come ho avuto a che fare con esso:


In primo luogo, penso che ti sei perso un dettaglio importante: ci sono nessuna variabile globale in Go. Lo widest scope you can have for a variable è l'ambito del pacchetto. Gli unici veri globali in Go sono predeclared identifiers come true e false (e non puoi cambiarli o crearne uno tuo).

Quindi, è perfettamente corretto impostare una variabile con scope su package main per mantenere il contesto per il programma. Venendo da uno sfondo C/C++, mi ci è voluto un po 'di tempo per abituarmi. Poiché le variabili sono scope del pacchetto, non soffrono di the problems of global variables. Se qualcosa in un altro pacchetto ha bisogno di una tale variabile, dovrai passarla esplicitamente.

Non aver paura di utilizzare le variabili del pacchetto quando ha senso. Questo può aiutarti a ridurre la complessità del tuo programma, e in molti casi rendere i tuoi gestori personalizzati molto più semplici (dove chiamare http.HandlerFunc() e passare una chiusura è sufficiente).

Tale un semplice gestore potrebbe assomigliare a questo:

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) 
    }) 
} 

ed essere usato da:

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

Se le vostre esigenze sono più complesse, potrebbe essere necessario implementare il proprio http.Handler . Ricorda che uno http.Handler è solo un'interfaccia che implementa ServeHTTP(w http.ResponseWriter, r *http.Request).

Questo non è testato, ma dovrebbe ottenere circa il 95% del tragitto:

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} 
    } 
} 

usarlo:

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

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

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

Per un esempio handler più sviluppata vedere basicAuth in goji/httpauth, da cui questo esempio è stato spudoratamente derubato.


Qualche ulteriore lettura:

Problemi correlati