英文:
Including context objects through multiple HTTP handlers in golang
问题
我刚刚阅读了这篇关于创建函数类型并在该函数上实现.ServeHTTP()
方法以处理错误的博文。例如:
type appError struct {
Error error
Message string
Code int
}
type appHandler func(http.ResponseWriter, *http.Request) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
func init() {
http.Handle("/view", appHandler(viewRecord)) //viewRecord is an appHandler function
}
我喜欢这种方法,但我无法在处理程序层中概念化地包含一个上下文对象。例如:
func init() {
http.Handle("/view", AuthHandler(appHandler(viewRecord)))
}
AuthHandler
可能会创建一个 &SessionToken{User: user}
对象,并将其设置在每个请求的 context.Context 对象中。但我无法弄清楚如何将其传递给 viewRecord
处理程序。有什么想法吗?
英文:
I just read this blog post about creating a function type and implementing the .ServeHTTP()
method on that function to be able to handle errors. For example:
type appError struct {
Error error
Message string
Code int
}
type appHandler func(http.ResponseWriter, *http.Request) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
func init() {
http.Handle("/view", appHandler(viewRecord)) //viewRecord is an appHandler function
}
I like this approach but I can't conceptually figure out how to include a context object through handler layers. For example:
func init() {
http.Handle("/view", AuthHandler(appHandler(viewRecord)))
}
AuthHandler
would likely create a &SessionToken{User: user}
object and set that in a context.Context object for each request. I can't work out how to get that to the viewRecord
handler though. Ideas?
答案1
得分: 13
我可以想到几种方法来实现这个。
传递上下文
首先,你可以更改函数签名以接受上下文参数。
type appHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r, nil); e != nil { // e 是 *appError,而不是 os.Error。
http.Error(w, e.Message, e.Code)
}
}
现在,我假设 AuthHandler 与身份验证有关,并在上下文对象中设置用户。你可以创建另一种处理程序类型来设置上下文,像这样:
type authHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 在这里进行身份验证
uid := 1
// 根据需要设置上下文
parent := context.TODO()
ctx := context.WithValue(parent, userIdKey, uid)
if e := fn(w, r, ctx); e != nil { // e 是 *appError,而不是 os.Error。
http.Error(w, e.Message, e.Code)
}
}
这样,你可以按照以下方式使用它:
func init() {
http.Handle("/view", appHandler(viewRecord)) // 不需要身份验证
http.Handle("/viewAuth", authHandler(viewRecord)) // 需要身份验证
}
这是完整的代码:
package main
import (
"fmt"
"net/http"
"code.google.com/p/go.net/context"
)
type appError struct {
Error error
Message string
Code int
}
type key int
const userIdKey key = 0
type appHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r, nil); e != nil { // e 是 *appError,而不是 os.Error。
http.Error(w, e.Message, e.Code)
}
}
type authHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 在这里进行身份验证
uid := 1
// 根据需要设置上下文
parent := context.TODO()
ctx := context.WithValue(parent, userIdKey, uid)
if e := fn(w, r, ctx); e != nil { // e 是 *appError,而不是 os.Error。
http.Error(w, e.Message, e.Code)
}
}
func viewRecord(w http.ResponseWriter, r *http.Request, c context.Context) *appError {
if c == nil {
fmt.Fprintf(w, "用户未登录")
} else {
uid := c.Value(userIdKey)
fmt.Fprintf(w, "用户已登录,用户ID:%d", uid)
}
return nil
}
func init() {
http.Handle("/view", appHandler(viewRecord)) // viewRecord 是一个 appHandler 函数
http.Handle("/viewAuth", authHandler(viewRecord)) // viewRecord 是一个 authHandler 函数
}
func main() {
http.ListenAndServe(":8080", nil)
}
创建映射上下文
而不是传递上下文,你可以创建一个映射:
var contexts map[*http.Request]context.Context
然后在 view
函数中使用 contexts[r]
获取上下文。
但是,由于映射不是线程安全的,对映射的访问必须受到互斥锁的保护。
而且,你知道吗,这正是 gorilla context 正在为你做的,我认为这是更好的方法。
https://github.com/gorilla/context/blob/master/context.go#l20-28
这是完整的代码:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/context"
)
type appError struct {
Error error
Message string
Code int
}
type key int
const userIdKey key = 0
type appHandler func(http.ResponseWriter, *http.Request) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e 是 *appError,而不是 os.Error。
http.Error(w, e.Message, e.Code)
}
}
type authHandler func(http.ResponseWriter, *http.Request) *appError
func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 在这里进行身份验证
uid := 1
context.Set(r, userIdKey, uid)
if e := fn(w, r); e != nil { // e 是 *appError,而不是 os.Error。
http.Error(w, e.Message, e.Code)
}
}
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
if uid, ok := context.GetOk(r, userIdKey); !ok {
fmt.Fprintf(w, "用户未登录")
} else {
fmt.Fprintf(w, "用户已登录,用户ID:%d", uid)
}
return nil
}
func init() {
http.Handle("/view", appHandler(viewRecord)) // 不需要身份验证
http.Handle("/viewAuth", authHandler(viewRecord)) // 需要身份验证
}
func main() {
http.ListenAndServe(":8080", nil)
}
你还可以选择使用包装函数而不是类型函数来进行身份验证:
func AuthHandler(h appHandler) appHandler {
return func(w http.ResponseWriter, r *http.Request) *appError {
// 在这里进行身份验证
uid := 1
context.Set(r, userIdKey, uid)
return h(w, r)
}
}
func init() {
http.Handle("/view", appHandler(viewRecord)) // 不需要身份验证
http.Handle("/viewAuth", appHandler(AuthHandler(viewRecord))) // 需要身份验证
}
英文:
I can think of a couple of approaches to do this.
Passing the context
first you can change the signature to accept context
type appHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r, nil); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
Now I assume the AuthHandler has to do with authentication and setup the user in the context object.
What you could do is create another type handler which setup the context. like this
type authHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// setup authentication here
uid := 1
// setup the context the way you want
parent := context.TODO()
ctx := context.WithValue(parent, userIdKey, uid)
if e := fn(w, r, ctx); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
This way you can use it in the following way
func init() {
http.Handle("/view", appHandler(viewRecord)) // don't require authentication
http.Handle("/viewAuth", authHandler(viewRecord)) // require authentication
}
This is the complete code
package main
import (
"fmt"
"net/http"
"code.google.com/p/go.net/context"
)
type appError struct {
Error error
Message string
Code int
}
type key int
const userIdKey key = 0
type appHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r, nil); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
type authHandler func(http.ResponseWriter, *http.Request, context.Context) *appError
func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// setup authentication here
uid := 1
// setup the context the way you want
parent := context.TODO()
ctx := context.WithValue(parent, userIdKey, uid)
if e := fn(w, r, ctx); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
func viewRecord(w http.ResponseWriter, r *http.Request, c context.Context) *appError {
if c == nil {
fmt.Fprintf(w, "User are not logged in")
} else {
uid := c.Value(userIdKey)
fmt.Fprintf(w, "User logged in with uid: %d", uid)
}
return nil
}
func init() {
http.Handle("/view", appHandler(viewRecord)) // viewRecord is an appHandler function
http.Handle("/viewAuth", authHandler(viewRecord)) // viewRecord is an authHandler function
}
func main() {
http.ListenAndServe(":8080", nil)
}
create map context
Instead of passing the context, you create
var contexts map[*http.Request]context.Context
and get the context in view
with contexts[r]
.
But because of map is not thread safe, access to the map must be protected with mutex.
And guess what, this is what gorilla context is doing for you, and I think it's better approach
https://github.com/gorilla/context/blob/master/context.go#l20-28
this is the full code
package main
import (
"fmt"
"net/http"
"github.com/gorilla/context"
)
type appError struct {
Error error
Message string
Code int
}
type key int
const userIdKey key = 0
type appHandler func(http.ResponseWriter, *http.Request) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
type authHandler func(http.ResponseWriter, *http.Request) *appError
func (fn authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// setup authentication here
uid := 1
context.Set(r, userIdKey, uid)
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
http.Error(w, e.Message, e.Code)
}
}
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
if uid, ok := context.GetOk(r, userIdKey); !ok {
fmt.Fprintf(w, "User are not logged in")
} else {
fmt.Fprintf(w, "User logged in with uid: %d", uid)
}
return nil
}
func init() {
http.Handle("/view", appHandler(viewRecord)) // don't require authentication
http.Handle("/viewAuth", authHandler(viewRecord)) // require authentication
}
func main() {
http.ListenAndServe(":8080", nil)
}
you can also opt for wrapper function instead of type function for auth
func AuthHandler(h appHandler) appHandler {
return func(w http.ResponseWriter, r *http.Request) *appError {
// setup authentication here
uid := 1
context.Set(r, userIdKey, uid)
return h(w, r)
}
}
func init() {
http.Handle("/view", appHandler(viewRecord)) // don't require authentication
http.Handle("/viewAuth", appHandler(AuthHandler(viewRecord))) // require authentication
}
答案2
得分: 2
使用r.Context()
,该函数自Go 1.7版本开始可用。
请参阅https://golang.org/pkg/net/http/#Request.Context
英文:
Use r.Context()
, which is available since Go 1.7.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论