【发布时间】:2015-09-13 09:28:29
【问题描述】:
我对 golang 相当陌生,并且正在尝试找出以惯用方式做到这一点的最佳方法。
我有一组静态定义并传递给gorilla/mux 的路由。我用一些东西来包装每个处理函数来计时请求和处理恐慌(主要是为了让我能理解包装是如何工作的)。
我希望他们每个人都能够访问“上下文”——一个将是一个 http-server 的结构,它可能包含数据库句柄、配置等内容。我不想要什么要做的是使用静态全局变量。
按照我目前的做法,我可以让包装器访问上下文结构,但我看不到如何将它放入实际的处理程序中,因为它希望它成为 http.HandlerFunc。我想我能做的就是将http.HandlerFunc 转换为我自己的类型,它是Context 的接收器(并且对包装器执行类似的操作,但是(经过多次尝试)我无法将Handler() 转换为接受这个。
我不禁认为我在这里遗漏了一些明显的东西。代码如下。
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))
}
这是 a 的方法,但它看起来很可怕。我不禁认为必须有更好的方法来做到这一点 - 也许是子类(?)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))
}
【问题讨论】:
-
看看github.com/alexedwards/stack - 它为堆叠中间件(处理程序)提供了一个很好的抽象,并提供了一种通过链传递上下文的简单方法。
-
Gorilla 本身有一个上下文包。
-
@freeformz Gorilla 的上下文包用于请求上下文,而不是程序上下文。特别是,它会在请求结束时自动清除(如果您使用的是 gorilla/mux)。