【问题标题】:Passing context to gorilla mux - go idioms将上下文传递给 gorilla mux - go idioms
【发布时间】: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)。

标签: go gorilla


【解决方案1】:

我正在学习 Go,目前遇到了一个几乎相同的问题,这就是我处理它的方式:


首先,我认为您错过了一个重要的细节:Go 中没有全局变量。 widest scope you can have for a variable 是包范围。 Go 中唯一真正的全局变量是 predeclared identifiers,例如 truefalse(您无法更改这些或自己制作)。

因此,将范围为package main 的变量设置为保存程序的上下文是非常好的。来自 C/C++ 背景,这花了我一点时间来适应。由于变量是包作用域的,它们不会受到the problems of global variables 的影响。如果另一个包中的某些东西需要这样的变量,则必须显式传递它。

在有意义的时候不要害怕使用包变量。这可以帮助您降低程序的复杂性,并且在很多情况下使您的自定义处理程序更加简单(调用 http.HandlerFunc() 并传递一个闭包就足够了)。

这样一个简单的处理程序可能如下所示:

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

并被:

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

如果您的需求更复杂,您可能需要实现自己的http.Handler。请记住,http.Handler 只是一个实现ServeHTTP(w http.ResponseWriter, r *http.Request) 的接口。

这是未经测试的,但应该能让你完成大约 95% 的路:

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

使用它:

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

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

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

有关更发达的处理程序示例,请参阅basicAuth in goji/httpauth,此示例被无耻地抄袭。


进一步阅读:

【讨论】:

    猜你喜欢
    • 2015-05-03
    • 1970-01-01
    • 2014-12-21
    • 2015-04-02
    • 2015-04-18
    • 1970-01-01
    • 2020-06-14
    • 2022-01-20
    • 1970-01-01
    相关资源
    最近更新 更多