31
loading...
This website collects cookies to deliver better user experience
context.WithValue
. This function creates a new context based on a parent context and adds a value to a given key. You can think about the internal implementation as if the context contained a map inside of it, so you can add and retrieve values by key. This is very powerful as it allows you to store any type of data inside the context. Here’s an example of adding and retrieving data with a context.package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
ctx = addValue(ctx)
readValue(ctx)
}
func addValue(ctx context.Context) context.Context {
return context.WithValue(ctx, "key", "test-value")
}
func readValue(ctx context.Context) {
val := ctx.Value("key")
fmt.Println(val)
}
context.Context
struct. This means that you have to remember to work with the returned value from operations and possibly override old contexts with new ones. This is a key design in immutability. context.Context
to concurrent functions and as long as you properly manage the context you are passing on, it’s good way to share scoped values between those concurrent functions (meaning that each context will keep their own values on its scope). That’s exactly what net/http
package does when handling HTTP requests. To elaborate on that let’s have a look into the next example.http.Request
contains a context which can carry scoped values throughout the HTTP pipeline. It is very common to see code where middlewares are added to the HTTP pipeline and the results of the middlewares are added to the http.Request
context. This is a very useful technique as you can rely on something you know happened to in your pipeline already on later stages. This also enables you to use generic code to handle HTTP request, while respecting the scope where you want to share the data (instead of sharing data on global variables for example). Here’s an example of a middleware that leverages the request context.package main
import (
"context"
"log"
"net/http"
"github.com/google/uuid"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.Use(guidMiddleware)
router.HandleFunc("/ishealthy", handleIsHealthy).Methods(http.MethodGet)
http.ListenAndServe(":8080", router)
}
func handleIsHealthy(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
uuid := r.Context().Value("uuid")
log.Printf("[%v] Returning 200 - Healthy", uuid)
w.Write([]byte("Healthy"))
}
func guidMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
uuid := uuid.New()
r = r.WithContext(context.WithValue(r.Context(), "uuid", uuid))
next.ServeHTTP(w, r)
})
}
context.WithCancel(ctx)
passing your context as parameter. This will return a new context and a cancel function. To cancel that context you only need to call the cancel function.package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
neturl "net/url"
"time"
)
func queryWithHedgedRequestsWithContext(urls []string) string {
ch := make(chan string, len(urls))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, url := range urls {
go func(u string, c chan string) {
c <- executeQueryWithContext(u, ctx)
}(url, ch)
select {
case r := <-ch:
cancel()
return r
case <-time.After(21 * time.Millisecond):
}
}
return <-ch
}
func executeQueryWithContext(url string, ctx context.Context) string {
start := time.Now()
parsedURL, _ := neturl.Parse(url)
req := &http.Request{URL: parsedURL}
req = req.WithContext(ctx)
response, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err.Error())
return err.Error()
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
fmt.Printf("Request time: %d ms from url%s\n", time.Since(start).Nanoseconds()/time.Millisecond.Nanoseconds(), url)
return fmt.Sprintf("%s from %s", body, url)
}
context.Context
as argument, they either actively act on the context (like checking if it was cancelled) or they pass it to an underlying function that deals with it (in this case the Do function that receives the context through the http.Request
).context.WithTimeout(ctx, time)
passing your context and the actual timeout. Like this:ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
// Server implementation receiving metadata
func (*server) Sum(ctx context.Context, req *calculatorpb.SumRequest) (*calculatorpb.SumResponse, error) {
log.Printf("Sum rpc invoked with req: %v\n", req)
md, _ := metadata.FromIncomingContext(ctx)
log.Printf("Metadata received: %v", md)
return &calculatorpb.SumResponse{
Result: req.NumA + req.NumB,
}, nil
}
// Client implementation sending metadata
func sum(c calculatorpb.CalculatorServiceClient) {
req := &calculatorpb.SumRequest{
NumA: 3,
NumB: 10,
}
ctx := metadata.AppendToOutgoingContext(context.Background(), "user", "test")
res, err := c.Sum(ctx, req)
if err != nil {
log.Fatalf("Error calling Sum RPC: %v", err)
}
log.Printf("Response: %d\n", res.Result)
}
// Server implementation handling context cancellation
func (*server) Greet(ctx context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) {
log.Println("Greet rpc invoked!")
time.Sleep(500 * time.Millisecond)
if ctx.Err() == context.Canceled {
return nil, status.Error(codes.Canceled, "Client cancelled the request")
}
first := req.Greeting.FirstName
return &greetpb.GreetResponse{
Result: fmt.Sprintf("Hello %s", first),
}, nil
}
// Client implementation using timeout context cancellation
func greetWithTimeout(c greetpb.GreetServiceClient) {
req := &greetpb.GreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "Ricardo",
LastName: "Linck",
},
}
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
res, err := c.Greet(ctx, req)
if err != nil {
grpcErr, ok := status.FromError(err)
if ok {
if grpcErr.Code() == codes.DeadlineExceeded {
log.Fatal("Deadline Exceeded")
}
}
log.Fatalf("Error calling Greet RPC: %v", err)
}
log.Printf("Response: %s\n", res.Result)
}