Go HTTP Request Timeout with Context
Apply a per-request deadline with context.WithTimeout, classify timeout errors, and cap response reads in a Go HTTP client.
Code
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"time"
)
type fetchResult struct {
Status int
Body string
}
func fetchWithDeadline(ctx context.Context, client *http.Client, url string, timeout time.Duration) (fetchResult, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fetchResult{}, fmt.Errorf("build request: %w", err)
}
resp, err := client.Do(req)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return fetchResult{}, fmt.Errorf("fetch %s: deadline exceeded after %s: %w", url, timeout, context.DeadlineExceeded)
}
return fetchResult{}, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return fetchResult{}, fmt.Errorf("read body: %w", err)
}
return fetchResult{Status: resp.StatusCode, Body: string(body)}, nil
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ok")
})
mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
select {
case <-time.After(200 * time.Millisecond):
fmt.Fprint(w, "late")
case <-r.Context().Done():
}
})
server := httptest.NewServer(mux)
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
ctx := context.Background()
result, err := fetchWithDeadline(ctx, client, server.URL+"/fast", 500*time.Millisecond)
fmt.Printf("fast: status=%d body=%q err=%v\n", result.Status, result.Body, err)
result, err = fetchWithDeadline(ctx, client, server.URL+"/slow", 50*time.Millisecond)
fmt.Printf("slow: status=%d body=%q err=%v\n", result.Status, result.Body, err)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("slow request classified as a deadline error")
}
}Report a problem with this exampleOpens a pre-filled issue on GitHub.