在golang中通过多个HTTP处理程序包含上下文对象

huangapple go评论78阅读模式
英文:

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.

See https://golang.org/pkg/net/http/#Request.Context

huangapple
  • 本文由 发表于 2014年11月5日 12:01:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/26749489.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定