golangtutorialAll tutorials

Go HTTP Client: Timeouts, JSON, and Reliable Requests

Build reliable outbound HTTP requests in Go with client timeouts, context, JSON, status checks, body limits, and transport reuse.

Web Apis · Lesson 3Saved in this browser. No account required.

Go’s http.Client is safe for concurrent use and should usually be reused. A zero-value client has no overall timeout, so production code should configure one or rely on request deadlines deliberately.

Create and send a request

client := &http.Client{Timeout: 5 * time.Second}

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
	return err
}
req.Header.Set("Accept", "application/json")

res, err := client.Do(req)
if err != nil {
	return err
}
defer res.Body.Close()

Closing the body permits connection reuse. For small responses, read it fully; for large or untrusted responses, impose a limit.

Check status before decoding

if res.StatusCode < 200 || res.StatusCode >= 300 {
	body, _ := io.ReadAll(io.LimitReader(res.Body, 8<<10))
	return fmt.Errorf("upstream status %d: %s", res.StatusCode, body)
}

Do not decode an error response into the success type. Limit captured error bodies so an upstream cannot force large allocations or logs.

Send JSON

payload, err := json.Marshal(input)
if err != nil { return err }

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")

Configure transport carefully

The default transport already pools connections. Clone it before customization rather than constructing an incomplete transport from scratch. Configure dial, TLS, response-header, and idle-connection timeouts based on the service’s latency budget.

Retries are safe only when the operation is idempotent or uses an idempotency key. Retry a narrow set of transient failures with backoff and a total deadline; never create an infinite retry loop.