Created
February 1, 2026 21:09
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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