将上下文传递给接口方法 [英] Passing Context to Interface Methods
问题描述
上周受到这篇文章的启发,我正在重新构建应用程序我必须更多地将上下文(数据库池,会话存储等)传递给我的处理程序。
然而,我遇到的一个问题是没有全局模板映射,我的自定义处理程序类型(满足 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 $ c在这两种情况下,$ c> 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屋!