Gin: Gin does not support Context for cancellation

Created on 18 Nov 2014  路  12Comments  路  Source: gin-gonic/gin

Check this out:
https://blog.golang.org/context

Future versions of Go will quite possibly make Contexts, used for canceling work and passing data around widely branching distributed systems across protocol and machine boundaries, built into the core libraries and/or language. The Go team at Google is pushing for all new code written in the community to use these Context objects so we have interoperability between open source libraries in the wild and Google Go code. The author of this article (Sameer Ajmani) just gave a great talk on this at GothamGo in NYC. I just want to call attention to the issue since Gin is an awesome, forward-looking framework, and it would be a shame to not be able to easily use it in a large, distributed system with various actors written in Go simply because it was not adapted to use Contexts. I will post back on this issue with a link to Sameer's talk when it's online.

Thanks for reading!

enhancement

Most helpful comment

This is a direct port of the Google search example provided by the Go Team.
https://blog.golang.org/context/server/server.go

package main

import (
    "html/template"
    "time"

    "github.com/gin-gonic/gin"
    "golang.org/x/blog/content/context/google"
    "golang.org/x/blog/content/context/userip"
    "golang.org/x/net/context"
)

func main() {
    router := gin.Default()
    router.SetHTMLTemplate(resultsTemplate)
    router.GET("/search", handleSearch)
    router.Run(":8080")
}

// handleSearch handles URLs like /search?q=golang&timeout=1s by forwarding the
// query to google.Search. If the query param includes timeout, the search is
// canceled after that duration elapses.
func handleSearch(c *gin.Context) {
    var (
        ctx    context.Context
        cancel context.CancelFunc
    )
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
    if err == nil {
        // The request has a timeout, so create a context that is
        // canceled automatically when the timeout expires.
        ctx, cancel = context.WithTimeout(c, timeout)
    } else {
        ctx, cancel = context.WithCancel(c)
    }
    defer cancel() // Cancel ctx as soon as handleSearch returns.

    // Check the search query.
    query := c.FormValue("q")
    if query == "" {
        c.String(400, "no query")
        return
    }

    // Store the user IP in ctx for use by code in other packages.
    userIP, err := userip.FromRequest(req)
    if err != nil {
        c.String(400, err.Error())
        return
    }

    ctx = userip.NewContext(ctx, userIP)

    // Run the Google search and print the results.
    start := time.Now()
    results, err := google.Search(ctx, query)
    elapsed := time.Since(start)
    if err != nil {
        c.String(400, err.Error())
        return
    }
    c.HTML(200, "results", struct {
        Results          google.Results
        Timeout, Elapsed time.Duration
    }{
        Results: results,
        Timeout: timeout,
        Elapsed: elapsed,
    })
}

var resultsTemplate = template.Must(template.New("results").Parse(`
<html>
<head/>
<body>
  <ol>
  {{range .Results}}
    <li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li>
  {{end}}
  </ol>
  <p>{{len .Results}} results in {{.Elapsed}}; timeout {{.Timeout}}</p>
</body>
</html>
`))

All 12 comments

By the way, Gin's current Context objects would be a perfect place to bring in the Google Context notion. The foundation has already been laid.

I assign it to @manucorporat , so he can know this when developing next version of Gin. Thanks @eldilibra.

Grazie! :gem:

Location of documentation regarding this has moved.

https://godoc.org/golang.org/x/net/context

The concept of a context that Go has put forward is simply an interface, a heavily documented interface, but one nevertheless, so it is very important that the implementation of this matches the documentation they provide.

I'd be willing to do some work on this on a touch and go basis.

This is a pretty important change to get in before the decision to lock the API comes for 1.0 as mentioned here. #204

@manucorporat Could this change please be considered for the version 1.0 API freeze? I love gin and I love net/context, and I would love to see them work together. Gin using net/context instead of its own implementation of context would be a huge step toward a universal context that can work across all packages.

@cmelbye @eldilibra @itsjamie
gin.Context conforms to the Context interface and it has an implementation like context.Background
https://github.com/gin-gonic/gin/commit/f9952b05457b006ff9efd3fe184a9b781e352017

This is a direct port of the Google search example provided by the Go Team.
https://blog.golang.org/context/server/server.go

package main

import (
    "html/template"
    "time"

    "github.com/gin-gonic/gin"
    "golang.org/x/blog/content/context/google"
    "golang.org/x/blog/content/context/userip"
    "golang.org/x/net/context"
)

func main() {
    router := gin.Default()
    router.SetHTMLTemplate(resultsTemplate)
    router.GET("/search", handleSearch)
    router.Run(":8080")
}

// handleSearch handles URLs like /search?q=golang&timeout=1s by forwarding the
// query to google.Search. If the query param includes timeout, the search is
// canceled after that duration elapses.
func handleSearch(c *gin.Context) {
    var (
        ctx    context.Context
        cancel context.CancelFunc
    )
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
    if err == nil {
        // The request has a timeout, so create a context that is
        // canceled automatically when the timeout expires.
        ctx, cancel = context.WithTimeout(c, timeout)
    } else {
        ctx, cancel = context.WithCancel(c)
    }
    defer cancel() // Cancel ctx as soon as handleSearch returns.

    // Check the search query.
    query := c.FormValue("q")
    if query == "" {
        c.String(400, "no query")
        return
    }

    // Store the user IP in ctx for use by code in other packages.
    userIP, err := userip.FromRequest(req)
    if err != nil {
        c.String(400, err.Error())
        return
    }

    ctx = userip.NewContext(ctx, userIP)

    // Run the Google search and print the results.
    start := time.Now()
    results, err := google.Search(ctx, query)
    elapsed := time.Since(start)
    if err != nil {
        c.String(400, err.Error())
        return
    }
    c.HTML(200, "results", struct {
        Results          google.Results
        Timeout, Elapsed time.Duration
    }{
        Results: results,
        Timeout: timeout,
        Elapsed: elapsed,
    })
}

var resultsTemplate = template.Must(template.New("results").Parse(`
<html>
<head/>
<body>
  <ol>
  {{range .Results}}
    <li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li>
  {{end}}
  </ol>
  <p>{{len .Results}} results in {{.Elapsed}}; timeout {{.Timeout}}</p>
</body>
</html>
`))

@manucorporat Oh, very cool! I forgot that context.Context was an interface. That's pretty slick!

yeah! @cmelbye so the approach is to make gin.Context behave like context.Background + Value.
This allows us to keep the Context size the same (we do not have to add a pointer to another Background context.

The PR implementing the support for cancellation has been reverted.

Is there any plan for another attempt ?

Hi! I am also interested in this port, since we are starting a project and gin seems the perfect framework, but we need to interact with other libraries that need the use of go Context.

So, I did not realize that the go Context is simply accessible via c.Request.Context().

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nxvl picture nxvl  路  3Comments

frederikhors picture frederikhors  路  3Comments

oryband picture oryband  路  3Comments

xpbliss picture xpbliss  路  3Comments

wangcn picture wangcn  路  3Comments