02/03/2020 - GO
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.
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.
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
}
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
}
)
}
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
}
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)
{}
}
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
}
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
}
)
}