In this example we are going to clone HTTP request context with all its context key/value pairs. However, the important point is that we are going discard cancel or deadline propagation. The reason is because, when the response is returned or the context is cancelled for whatever reason, we don't want our context being terminated and prematurely break our application.


There are two options here. The first one is where you manually set all the key/value pairs on a brand new context and ignore HTTP request context. However, if you don't know key/value then this is useless. The second option is what we described above so we are going to detach and clone existing HTTP request context.


One thing you need to pay attention to. If the HTTP request context has nothing to do with your background task where a context is needed, just create a brand new context. Do not clone HTTP request context.


Our example is simple. We will have two middleware where we set a few key/value pairs within the HTTP request context. We will also add timeout/deadline on it. We will then print how original and detached/clones context look like.


Structure


├── context
│   └── detached.go
├── main.go
└── middleware
├── one.go
└── two.go

Files


detached.go


package context

import (
"context"
"time"
)

type detached struct {
ctx context.Context
}

func (detached) Deadline() (time.Time, bool) {
return time.Time{}, false
}

func (detached) Done() <-chan struct{} {
return nil
}

func (detached) Err() error {
return nil
}

func (d detached) Value(key interface{}) interface{} {
return d.ctx.Value(key)
}

func Detach(ctx context.Context) context.Context {
return detached{ctx: ctx}
}

one.go


Do not set context key/value as I do below. Use context keys properly.


package middleware

import (
"context"
"net/http"
"time"
)

func One(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "one-key", "one-value")

// This will set a deadline on the context.
ctx, cancel := context.WithTimeout(ctx, time.Millisecond*1)
_ = cancel

h.ServeHTTP(w, r.WithContext(ctx))
})
}

two.go


Do not set context key/value as I do below. Use context keys properly.


package middleware

import (
"context"
"net/http"
"time"
)

func Two(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "two-key", "two-value")

// This will set a deadline on the context.
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Millisecond*time.Duration(1)))
_ = cancel

h.ServeHTTP(w, r.WithContext(ctx))
})
}

main.go


package main

import (
"fmt"
"log"
"net/http"

"sport/context"
"sport/middleware"
)

func main() {
log.Println("sport running...")

handler := http.NewServeMux()
handler.HandleFunc("/", home)

log.Fatalln(http.ListenAndServe(":8181", middleware.One(middleware.Two(handler))))
}

func home(w http.ResponseWriter, r *http.Request) {
log.Println("home")

// Detached context.
detachedCtx := context.Detach(r.Context())
detachedDeadline, ok := detachedCtx.Deadline()
fmt.Println("DETACHED ---")
fmt.Println("Deadline():", detachedDeadline, ok)
fmt.Println("Err():", r.Context().Err())
log.Println(detachedCtx.Value("one-key"))
log.Println(detachedCtx.Value("two-key"))

// Original context.
originalCtx := r.Context()
originalDeadline, ok := originalCtx.Deadline()
fmt.Println("ORIGINAL ---")
fmt.Println("Deadline():", originalDeadline, ok)
fmt.Println("Err():", r.Context().Err())
log.Println(originalCtx.Value("one-key"))
log.Println(originalCtx.Value("two-key"))
}

Test


When you call http://localhost:8181 the output in terminal should look like the one below. As you can see, our detached/cloned context discarded timeout/deadline info so it won't be cancelled anymore.


DETACHED ---
Deadline(): 0001-01-01 00:00:00 +0000 UTC false
Err():
2020/09/15 12:42:14 one-value
2020/09/15 12:42:14 two-value
ORIGINAL ---
Deadline(): 2020-09-15 12:42:14.746067 +0100 BST m=+5.217963805 true
Err():
2020/09/15 12:42:14 one-value
2020/09/15 12:42:14 two-value