Correct use of go context.Context

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

Correct use of go context.Context

问题

我刚刚阅读了这篇文章:在Go中构建自己的Web框架,为了在处理程序之间共享值,我选择了context.Context,并且我正在以下方式中使用它来在处理程序和中间件之间共享值:

type appContext struct {
    db     *sql.DB
    ctx    context.Context
    cancel context.CancelFunc
}

func (c *appContext) authHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        defer c.cancel() // 这感觉有点奇怪
        authToken := r.Header.Get("Authorization") // 这是伪造的表单
        c.ctx = getUser(c.ctx, c.db, authToken) // 这也感觉有点奇怪
        next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
}

func (c *appContext) adminHandler(w http.ResponseWriter, r *http.Request) {
    defer c.cancel()
    user := c.ctx.Value(0).(user)
    json.NewEncoder(w).Encode(user)
}

func getUser(ctx context.Context, db *sql.DB, token string) context.Context {
    // 这个函数模拟了对数据库的访问
    return context.WithValue(ctx, 0, user{Nome: "默认用户"})
}

func main() {
    db, err := sql.Open("my-driver", "my.db")
    if err != nil {
        panic(err)
    }
    ctx, cancel := context.WithCancel(context.Background())
    appC := appContext{db, ctx, cancel}
    //....
}

一切都正常工作,处理程序加载速度比使用gorilla/context更快,所以我的问题是:

  1. 这种方法安全吗?
  2. 是否真的需要以我这种方式延迟执行c.cancel()函数?
  3. 我可以使用它来实现一个自定义的Web框架,通过使用类似结构体的控制器来与模型共享值吗?
英文:

I just read the article: Build You Own Web Framework In Go and for sharing values among handlers I picked the context.Context and I'm using it in the following way to share values across handlers and middlewares:

type appContext struct {
    db     *sql.DB
    ctx    context.Context
    cancel context.CancelFunc
 }


func (c *appContext)authHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request {
        defer c.cancel() //this feels weird
        authToken := r.Header.Get("Authorization") // this fakes a form
        c.ctx = getUser(c.ctx, c.db, authToken) // this also feels weird
        next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
}

func (c *appContext)adminHandler(w http.ResponseWriter, r *http.Request) {
    defer c.cancel()
    user := c.ctx.Value(0).(user)
    json.NewEncoder(w).Encode(user)
}

func getUser(ctx context.Context, db *sql.DB, token string) context.Context{
    //this function mimics a database access
    return context.WithValue(ctx, 0, user{Nome:"Default user"})
}

func main() {
    db, err := sql.Open("my-driver", "my.db")
    if err != nil {
        panic(err)
    }
    ctx, cancel := context.WithCancel(context.Background())
    appC := appContext{db, ctx, cancel}
    //....
}

Everything is working and handlers are loading faster than using gorilla/context So my questions are:

  1. Is this approach safe?
  2. Is it really necessary to defer the c.cancel() function the way I'm doing it?
  3. Can I use it to implement a custom web framework by using controllers like struct to share values with models?

答案1

得分: 2

注意:go 1.7.0-rc2在如何释放与上下文相关的资源方面进行了一些澄清(Sameer Ajmani):

> 一些用户没有意识到使用CancelFunc创建Context会将子树附加到父级,并且该子树直到调用CancelFunc或取消父级时才会被释放

> 在软件包文档中早期明确这一点,以便学习该软件包的人具有正确的概念模型。

文档现在包括了

> 服务器的传入请求应该创建一个Context,对服务器的传出调用应该接受一个Context
它们之间的函数调用链必须传播Context,可以用WithCancelWithDeadlineWithTimeoutWithValue创建一个派生的Context来替换它。
这些Context值形成了一棵树:当一个Context被取消时,所有从它派生的Context也会被取消

> WithCancelWithDeadlineWithTimeout函数返回一个派生的Context和一个CancelFunc
调用CancelFunc会取消新的Context和任何从它派生的Context,将Context从父级的树中移除,并停止任何关联的定时器。
如果未调用CancelFunc,则会泄漏相关资源,直到取消父Context或定时器触发。

英文:

Note: go 1.7.0-rc2 does clarify a bit how to release resources associated with Contexts (Sameer Ajmani):

> Some users don't realize that creating a Context with a CancelFunc attaches a subtree to the parent, and that that subtree is not released until the CancelFunc is called or the parent is canceled.

> Make this clear early in the package docs, so that people learning about this package have the right conceptual model.

The documentation now includes:

> Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context.
The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue.
These Context values form a tree: when a Context is canceled, all Contexts derived from it are also canceled.

> The WithCancel, WithDeadline, and WithTimeout functions return a derived Context and a CancelFunc.
Calling the CancelFunc cancels the new Context and any Contexts derived from it, removes the Context from the parent's tree, and stops any associated timers.
Failing to call the CancelFunc leaks the associated resources until the parent Context is canceled or the timer fires.

答案2

得分: 1

你的代码有问题,因为你将用户存储在应用上下文中。在同时有多个用户的情况下,这样是行不通的。上下文必须与请求相关联,以免被其他请求覆盖。用户应该存储在请求上下文中。在我的文章中,我使用了以下的gorilla函数:context.Set(r, "user", user)。其中,r表示请求。

如果你想在你的应用中使用context.Context,你应该使用它们的gorilla封装(你可以在这篇文章的末尾找到:https://blog.golang.org/context)。

另外,你不需要使用带有取消功能的上下文。对于根上下文来说,context.Background()就足够了。

英文:

You have a problem with your code because you are storing the user into the app context. With multiple users at the same time, it doesn't work. The context must be related to the request to not be overwrote by other requests. The user must be stored in a request context. In my articles I use the following gorilla function: context.Set(r, "user", user). r is the request.

If you want to use context.Context in your app, you should use their gorilla wrapper (you can find it at the end of this article: https://blog.golang.org/context).

Also, you don't need a context with cancel. context.Background() is okay for the root context.

答案3

得分: 0

Context包的两个主要用途是:

  1. 用于存储请求范围的值 - 使用context.WithValue()
  2. 用于取消操作 - 使用context.WithCancel()、context.WithTimeout()、context.WithDeadline()

使用context形成一个以context.Background()为根的上下文树。WithValue()、context.WithCancel()、WithTimeout()、WithDeadline()是从根上下文派生出来的上下文,可以进一步划分。下面的示例可以清楚地说明如何正确使用上下文。我找到了这个指南,其中提供了上述所有内容的使用方法和适当的示例。

来源:https://golangbyexample.com/using-context-in-golang-complete-guide/

英文:

Two major use cases of Context package are:

  1. For storing request scoped values - Using context.WithValue()
  2. For cancellation - using context.WithCancel(), context.WithTimeout(), context.WithDeadline()

Use of context forms a context tree with context.Background() as root. WithValue() ,context.WithCancel(), WithTimeout(), WithDeadline() are derived context from root context which can be further divide. An example of each can make it clear so ast to what is the correct use context. Came across this guide which provides the use of all discussed above with proper examples.

Source: https://golangbyexample.com/using-context-in-golang-complete-guide/

huangapple
  • 本文由 发表于 2015年6月19日 08:20:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/30928002.html
匿名

发表评论

匿名网友

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

确定