The post Using Context Package in GO (Golang) – Complete Guide appeared first on Welcome To Golang By Example.
]]>Context is a package provided by GO. Let’s first understand some problems that existed already, and which context package tries to solve.
If you notice all the above problems are quite applicable to HTTP requests and but none the less these problems are also applicable to many different areas too.
For a web HTTP request, it needs to be canceled when the client has disconnected, or the request has to be finished within a specified timeout and also requests scope values such as request_id needs to be available to all downstream functions.
The core of the understanding context is knowing the Context interface
type Context interface {
//It retures a channel when a context is cancelled, timesout (either when deadline is reached or timeout time has finished)
Done() <-chan struct{}
//Err will tell why this context was cancelled. A context is cancelled in three scenarios.
// 1. With explicit cancellation signal
// 2. Timeout is reached
// 3. Deadline is reached
Err() error
//Used for handling deallines and timeouts
Deadline() (deadline time.Time, ok bool)
//Used for passing request scope values
Value(key interface{}) interface{}
}
context package function Background() returns a empty Context which implements the Context interface
Then what is the use context.Background(). context.Background() serves as the root of all context which will be derived from it. It will be more clear as we go along
The above two methods describe a way of creating new contexts. More context can be derived from these contexts. This is where context tree comes into the picture
Before understanding Context Tree please make sure that it is implicitly created in the background when using context. You will find no mention of in go context package itself.
Whenever you use context, then the empty Context got from context.Background() is the root of all context. context.ToDo() also acts like root context but as mentioned above it is more like a context placeholder for future use. This empty context has no functionality at all and we can add functionality by deriving a new context from this. Basically a new context is created by wrapping an already existing immutable context and adding additional information. Let's see some example of a context tree which gets created
Two level tree
rootCtx := context.Background()
childCtx := context.WithValue(rootCtx, "msgId", "someMsgId")
In above
Three level tree
rootCtx := context.Background()
childCtx := context.WithValue(rootCtx, "msgId", "someMsgId")
childOfChildCtx, cancelFunc := context.WithCancel(childCtx)
In above
Multi-level tree
rootCtx := context.Background()
childCtx1 := context.WithValue(rootCtx, "msgId", "someMsgId")
childCtx2, cancelFunc := context.WithCancel(childCtx1)
childCtx3 := context.WithValue(rootCtx, "user_id", "some_user_id)
In above:
Above three-level tree would look like below
As since it is a tree, it is also possible to create more childs for a particular node. For eg we can derive a new context childCtx4 from childCtx1
childCtx4 := context.WithValue(childCtx1, "current_time", "some_time)
Tree with above node added would like below:
At this very moment, it might not be clear how WithValue() or WithCancel() function is used. Right now just understand that whenever using context, a context tree is created with root as the emptyCtx . These functions will get clear as we move on
A derived context is can be created in 4 ways
Let's understand each of the above in details
Used for passing request-scoped values. The complete signature of the function is
withValue(parent Context, key, val interface{}) (ctx Context)
It takes in a parent context, key, value and returns a derived context This derived context has key associated with the value. Here the parent context can be either context.Background() or any other context. Further, any context which is derived from this context will have this value.
#Root Context
ctxRoot := context.Background() - #Root context
#Below ctxChild has acess to only one pair {"a":"x"}
ctxChild := context.WithValue(ctxRoot, "a", "x")
#Below ctxChildofChild has access to both pairs {"a":"x", "b":"y"} as it is derived from ctxChild
ctxChildofChild := context.WithValue(ctxChild, "b", "y")
Example:
Complete Working example of withValue(). In the below example, we are injecting a msgId for each incoming request. If you notice in below program
package main
import (
"context"
"net/http"
"github.com/google/uuid"
)
func main() {
helloWorldHandler := http.HandlerFunc(HelloWorld)
http.Handle("/welcome", inejctMsgID(helloWorldHandler))
http.ListenAndServe(":8080", nil)
}
//HelloWorld hellow world handler
func HelloWorld(w http.ResponseWriter, r *http.Request) {
msgID := ""
if m := r.Context().Value("msgId"); m != nil {
if value, ok := m.(string); ok {
msgID = value
}
}
w.Header().Add("msgId", msgID)
w.Write([]byte("Hello, world"))
}
func inejctMsgID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
msgID := uuid.New().String()
ctx := context.WithValue(r.Context(), "msgId", msgID)
req := r.WithContext(ctx)
next.ServeHTTP(w, req)
})
}
Simply do a curl call to the above request after running the above program
curl -v http://localhost/welcome
Here will be the response. Notice the MsgId that gets populated in the response headers. The injectMsgId function acts as middleware and injects a unique msgId to the request context.
curl -v http://localhost:8080/welcome
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /do HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Msgid: a03ff1d4-1464-42e5-a0a8-743c5af29837
< Date: Mon, 23 Dec 2019 16:51:01 GMT
< Content-Length: 12
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
Used for cancellation signals. Below is the signature of WithCancel() function
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
context.WithCancel() function returns two things
Only the creator of this context should call the cancel function. It is highly not recommended to pass around the cancel function. Lets understand withCancel with an example.
Example:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
cancelCtx, cancelFunc := context.WithCancel(ctx)
go task(cancelCtx)
time.Sleep(time.Second * 3)
cancelFunc()
time.Sleep(time.Second * 1)
}
func task(ctx context.Context) {
i := 1
for {
select {
case <-ctx.Done():
fmt.Println("Gracefully exit")
fmt.Println(ctx.Err())
return
default:
fmt.Println(i)
time.Sleep(time.Second * 1)
i++
}
}
}
Output:
1
2
3
Gracefully exit
context canceled
In the above program
Used for time-based cancellation. The signature of the function is
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
context.WithTimeout() function will
Lets see an example
Example:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
cancelCtx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
go task1(cancelCtx)
time.Sleep(time.Second * 4)
}
func task1(ctx context.Context) {
i := 1
for {
select {
case <-ctx.Done():
fmt.Println("Gracefully exit")
fmt.Println(ctx.Err())
return
default:
fmt.Println(i)
time.Sleep(time.Second * 1)
i++
}
}
}
Output:
1
2
3
Gracefully exit
context deadline exceeded
In the above program
Used for deadline-based cancellation. The signature of the function is
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
context.WithDeadline() function
Let's see an example
Example:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
cancelCtx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*5))
defer cancel()
go task(cancelCtx)
time.Sleep(time.Second * 6)
}
func task(ctx context.Context) {
i := 1
for {
select {
case <-ctx.Done():
fmt.Println("Gracefully exit")
fmt.Println(ctx.Err())
return
default:
fmt.Println(i)
time.Sleep(time.Second * 1)
i++
}
}
}
Output:
1
2
3
4
5
Gracefully exit
context deadline exceeded
In the above program
How to create the context:
Context Tree
Deriving a new context
Following is a list of best practices that you can follow while using a context.
The post Using Context Package in GO (Golang) – Complete Guide appeared first on Welcome To Golang By Example.
]]>