Skip to content

Instantly share code, notes, and snippets.

@brandonhimpfen
Created February 1, 2026 21:09
Show Gist options
  • Select an option

  • Save brandonhimpfen/7dfaf9e114fac5bc5ca0b634607c7e1a to your computer and use it in GitHub Desktop.

Select an option

Save brandonhimpfen/7dfaf9e114fac5bc5ca0b634607c7e1a to your computer and use it in GitHub Desktop.
Go POST JSON with context + timeouts, decode JSON response, and safe body limits (idiomatic net/http).
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"time"
)
func newHTTPClient() *http.Client {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
}
return &http.Client{
Transport: transport,
Timeout: 15 * time.Second,
}
}
// postJSON sends `reqBody` as JSON and decodes the JSON response into `out`.
// - `out` should be a pointer to a struct/map (e.g., &MyResp{}).
func postJSON(ctx context.Context, client *http.Client, url string, reqBody any, out any) error {
payload, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("marshal json: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
if err != nil {
return fmt.Errorf("new request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "go-http-post-json-context/1.0")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
// Limit body size to avoid unbounded reads
const maxBodySize = 2 << 20 // 2MB
bodyReader := io.LimitReader(resp.Body, maxBodySize)
// Read body once so we can include it in errors if needed
body, err := io.ReadAll(bodyReader)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("http status %d: %s", resp.StatusCode, string(body))
}
if out == nil {
return nil
}
if err := json.Unmarshal(body, out); err != nil {
return fmt.Errorf("unmarshal json: %w", err)
}
return nil
}
// ----------------------------------------------------------------------
// Example usage
// ----------------------------------------------------------------------
type ExampleRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type ExampleResponse struct {
JSON ExampleRequest `json:"json"` // httpbin.org/post echoes your JSON here
}
func main() {
client := newHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
url := "https://httpbin.org/post"
reqBody := ExampleRequest{
Name: "Brandon",
Email: "brandon@example.com",
}
var respBody ExampleResponse
if err := postJSON(ctx, client, url, reqBody, &respBody); err != nil {
log.Fatalf("postJSON failed: %v", err)
}
fmt.Printf("Echoed JSON: %+v\n", respBody.JSON)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment