If your client application is calling a server application, you want the response come back as fast as possible. However, if the operation is slow then you can cancel it. For such cases, you can use context WithTimeout or WithCancel features.


Application


package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)

func main() {
handler := http.NewServeMux()
handler.HandleFunc("/client/api/v1/users", controller)

log.Fatal(http.ListenAndServe(":8080", handler))
}

func controller(w http.ResponseWriter, _ *http.Request) {
res, err := users()
if err != nil {
_, _ = w.Write([]byte(err.Error()))
return
}

body, err := ioutil.ReadAll(res.Body)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
return
}
res.Body.Close()

_, _ = w.Write(body)
}

// See users() function below for examples.

Client way


Although this post is about context, you can handle the whole cancellation with http.Client.Timeout parameter as shown below.


func users() (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, "http://localhost:8090/server/api/v1/users", nil)
if err != nil {
return nil, err
}

client := http.Client{Timeout: 1 * time.Second}
res, err := client.Do(req)
if err != nil {
fmt.Printf("%t\n", err)
fmt.Printf("%T\n", err)

return nil, err
}

return res, nil
}

Test


If client timeout occurs, the error will a *url.Error instance and the content will be similar to below.


&{
%!t(string=Get)
%!t(string=http://localhost:8090/server/api/v1/users)
%!t(*http.httpError=&{
net/http: request canceled (Client.Timeout exceeded while awaiting headers)
true
}
)
}

WithTimeout


func users() (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, "http://localhost:8090/server/api/v1/users", nil)
if err != nil {
return nil, err
}

ctx, cancel := context.WithTimeout(context.Background(), 1 * time.Second)
defer cancel()

client := http.Client{}
res, err := client.Do(req.WithContext(ctx))
if err != nil {
fmt.Printf("%t\n", err)
fmt.Printf("%T\n", err)

return nil, err
}

return res, nil
}

Test


If client request context timeout occurs, the error will a *url.Error instance and the content will be similar to below.


&{
%!t(string=Get)
%!t(string=http://localhost:8090/server/api/v1/users)
{}
}

WithCancel


func users() (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, "http://localhost:8090/server/api/v1/users", nil)
if err != nil {
return nil, err
}

ctx, cancel := context.WithCancel(context.Background())
_ = time.AfterFunc(1 * time.Second, func() { cancel() })

client := http.Client{}
res, err := client.Do(req.WithContext(ctx))
if err != nil {
fmt.Printf("%t\n", err)
fmt.Printf("%T\n", err)

return nil, err
}

return res, nil
}

Test


If client request context cancellation occurs, the error will a `*url.Error` instance and the content will be similar to below.


&{
%!t(string=Get)
%!t(string=http://localhost:8090/server/api/v1/users)
%!t(*errors.errorString=&{
context canceled
}
)
}