将上下文传递给接口方法 [英] Passing Context to Interface Methods

查看:118
本文介绍了将上下文传递给接口方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

上周受到这篇文章的启发,我正在重新构建应用程序我必须更多地将上下文(数据库池,会话存储等)传递给我的处理程序。



然而,我遇到的一个问题是没有全局模板映射,我的自定义处理程序类型(满足 http.Handler )的 ServeHTTP 方法不能再访问该地图渲染一个模板。



我需要保留全局模板变量,或者重新定义我的自定义处理程序类型作为一个结构。



有没有更好的方法来达到这个目标?
$ b

func.go

 包主
$ b导入(
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){
//模板必须是全局的,我们可以在这里使用
status,err:= ah(w,r)
if err!= nil {
log.Printf(HTTP%d:%q ,status,err)
开关状态{
case http.StatusNotFound:
//会实际上在这里呈现一个http_404.tmpl...
http.NotFound(w,r)
case http.StatusInternalServerError:
//这里实际上会呈现一个http_500.tmpl
//(如上)
http.Error(w,http.StatusText(状态),状态)
默认值:
//实际上会在这里呈现一个http_error.tmpl
//(如上)
http.Error(w,http.StatusText(状态),状态)
}
}
}

func main(){
//这两个例子都是'nil'的例子
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,零
}

struct.go

  package main 

导入(
fmt
记录
净/ 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
}

//我们需要将我们的自定义处理程序类型定义为一个struct
类型appHandler struct {
处理程序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)
开关状态{
case http.StatusNotFound:
//实际上会在这里呈现一个http_404.tmpl...
http.NotFound(w,r)
大小写http.StatusInternalServerError:
//实际上会渲染一个http_500.tmpl 在这里
//(如上)
http.Error(w,http.StatusText(状态),状态)
默认值:
//实际上会渲染一个http_error。 tmplhere
//(如上)
http.Error(w,http.StatusText(status),status)
}
}
}

func main(){
//只是这个例子中的'nil'
context:=& appContext {db:nil,store:nil}

r:= web.New()
//有一点难看,但它有效。
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
}

是否有清洁剂如何将上下文实例传递给 ServeHTTP



请注意, go build -gcflags = -m 显示这两个选项在堆分配队列中似乎都不好:& appContext literal转义为堆(尽可能),尽管我的解释是基于结构的选项会在每个方法上传递第二个指针(至 context )请求 - 如果我在这里错了,请纠正我,因为我希望更好地理解这一点。我不是全部确信全局包在主包中是不好的(即不是李b)只要它们可以安全地以这种方式使用(只读/互斥/一个池),但我确实喜欢明确地通过上下文提供。

方案

经过与几位有帮助的Gophers讨论#go-nuts后,上面的方法是从我所能分辨出来的尽可能好。




  • 这个方法的con是我们传递给我们的上下文struct 两次的引用:曾经作为我们方法中的指针接收器,并且再次作为一个struct成员,所以 ServeHTTP 也可以访问它。

  • pro是我们可以扩展我们的结构类型来接受一个请求上下文结构,如果我们想这样做的话(比如 gocraft / web )。


请注意,我们不能将我们的处理程序定义为 appHandler func(ah * appHandler)IndexHandler(...)上的方法,因为我们需要在 ServeHTTP (即 ah.h(w,r))中调用处理程序。

  type appContext struct {
db * sqlx.DB
store * sessions.CookieStore
templates map [string] * template 。模板
}

类型appHandler struct {
句柄func(w http.ResponseWriter,r * http.Request)(int,error)
* appContext //嵌入,所以我们可以在我们的处理程序中调用app.db或app.store。


在main()...
context:=& appContext {db:nil,store:nil}
r.Get( /,appHandler {context.IndexHandler,context})
...

这是最重要的是,完全兼容 http.Handler ,所以我们仍然可以用通用中间件来包装我们的处理程序结构,如下所示: gzipHandler(appHandler {context。 (我仍然愿意接受其他建议!)


$ b

$ b $ b


更新

感谢在Reddit上的这个很好的回复我能够找到一个更好的解决方案,不需要传递两个引用我的 context 实例每个请求。



我们只需创建一个接受嵌入式上下文和我们的处理程序类型的结构,并且我们仍然满足 http.Handler 接口感谢 ServeHTTP 。处理程序不再是我们的 appContext 类型的方法,而只是接受它作为参数,这会导致稍微长一点的函数签名,但仍然明显且易于阅读。如果我们担心'打字',我们甚至会打破原因,因为我们不再有方法接收者担心。

  type appContext struct {
db * sqlx.DB
store * sessions.CookieStore
templates map [string] * template.Template

type appHandler struct {
* appContext $ b $ func(a * appContext,w http.ResponseWriter,r * http.Request)(int,error)
}

func(ah appHandler)ServeHTTP(w http .ResponseWriter,r * http.Request){
//我们现在可以在这里访问我们的上下文。
status,err:= ah.h(ah.appContext,w,r)
log.Printf(Hello!DB:%v,ah.db)
if err!= nil {
log.Printf(HTTP%d:%q,status,err)
开关状态{
case http.StatusNotFound:
// err:= ah。 renderTemplate(w,http_404.tmpl,nil)
http.NotFound(w,r)
case http.StatusInternalServerError:
// err:= ah.renderTemplate(w,http_500 .tmpl,nil)
http.Error(w,http.StatusText(status),status)
默认值:
// err:= ah.renderTemplate(w,http_error.tmpl ,无)
http.Error(w,http.StatusText(状态),状态)
}
}
}

func main {
context:=& appContext {
db:nil,
store:nil,
templates:nil,
}

r: = web.New()
//每次请求我们传递一次对上下文*的引用,并且它看起来更简单
r.Ge t(/,appHandler {context,IndexHandler})

func IndexHandler(a *'b',$'






$ graceful.ListenAndServe appContext,w http.ResponseWriter,r * http.Request)(int,error){
fmt.Fprintf(w,db is%q and store is%q \\\
,a.db,a。商店)
返回200,零
}


Somewhat inspired by this article last week, I'm toying with refactoring an application I have to more explicitly pass context (DB pools, session stores, etc) to my handlers.

However, one issue I'm having is that without a global templates map, the ServeHTTP method on my custom handler type (as to satisfy http.Handler) can no longer access the map to render a template.

I need to either retain the global templates variable, or re-define my custom handler type as a struct.

Is there a better way to achieve this?

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
}

Is there a cleaner way to pass the context instance to ServeHTTP?

Note that go build -gcflags=-m shows that neither option appears to be worse in teams of heap allocation: the &appContext literal escapes to the heap (as expected) in both cases, although my interpretation is that the struct-based option does pass a second pointer (to context) on each request—correct me if I'm wrong here as I'd love to get a better understanding of this.

I'm not wholly convinced that globals are bad in package main (i.e. not a lib) provided they are safe to use in that manner (read only/mutexes/a pool), but I do like clarity having to explicitly pass context provides.

解决方案

After some discussion with a couple of helpful Gophers on #go-nuts, the method above is about "as good as it gets" from what I can discern.

  • The "con" with this method is that we pass a reference to our context struct twice: once as a pointer receiver in our method, and again as a struct member so ServeHTTP can access it as well.
  • The "pro" is that we can extend our struct type to accept a request context struct if we wanted to do so (like gocraft/web does).

Note that we can't define our handlers as methods on appHandler i.e. func (ah *appHandler) IndexHandler(...) because we need to call the handler in ServeHTTP (i.e. ah.h(w,r)).

type appContext struct {
    db        *sqlx.DB
    store     *sessions.CookieStore
    templates map[string]*template.Template
}

type appHandler struct {
    handler func(w http.ResponseWriter, r *http.Request) (int, error)
    *appContext // Embedded so we can just call app.db or app.store in our handlers.
}

// In main() ...
context := &appContext{db: nil, store: nil}
r.Get("/", appHandler{context.IndexHandler, context}) 
...

This is also, most importantly, fully compatible with http.Handler so we can still wrap our handler struct with generic middleware like so: gzipHandler(appHandler{context.IndexHandler, context}).

(I'm still open to other suggestions however!)


Update

Thanks to this great reply on Reddit I was able to find a better solution that didn't require passing two references to my context instance per-request.

We instead just create a struct that accepts an embedded context and our handler type, and we still satisfy the http.Handler interface thanks to ServeHTTP. Handlers are no longer methods on our appContext type but instead just accept it as a parameter, which leads to a slightly longer function signature but is still "obvious" and easy to read. If we were concerned about 'typing' we're breaking even because we no longer have a method receiver to worry about.

type appContext struct {
    db    *sqlx.DB
    store *sessions.CookieStore
    templates map[string]*template.Template

type appHandler struct {
    *appContext
    h func(a *appContext, w http.ResponseWriter, r *http.Request) (int, error)
}

func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // We can now access our context in here.
    status, err := ah.h(ah.appContext, w, r)
    log.Printf("Hello! DB: %v", ah.db)
    if err != nil {
        log.Printf("HTTP %d: %q", status, err)
        switch status {
        case http.StatusNotFound:
            // err := ah.renderTemplate(w, "http_404.tmpl", nil)
            http.NotFound(w, r)
        case http.StatusInternalServerError:
            // err := ah.renderTemplate(w, "http_500.tmpl", nil)
            http.Error(w, http.StatusText(status), status)
        default:
            // err := ah.renderTemplate(w, "http_error.tmpl", nil)
            http.Error(w, http.StatusText(status), status)
        }
    }
}

func main() {
    context := &appContext{
        db:    nil,
        store: nil,
        templates: nil,
    }

    r := web.New()
    // We pass a reference to context *once* per request, and it looks simpler
    r.Get("/", appHandler{context, IndexHandler})

    graceful.ListenAndServe(":8000", r)
}

func IndexHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, error) {
    fmt.Fprintf(w, "db is %q and store is %q\n", a.db, a.store)
    return 200, nil
}

这篇关于将上下文传递给接口方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆