Example: HTTP Client Set Request Headers
HTTP request headers carry metadata such as authentication, content negotiation, and client identity.
In Go, request headers are stored in http.Request.Header and should normally be set before calling http.Client.Do.
http.Header is a map[string][]string. The []string matters because it determines the difference between Set and Add.
Quick Example
panic is used only to simplify the minimal demo. Production code must propagate and handle errors instead.
Header.Set vs Header.Add
Set
Set replaces all existing values for that header.
The header now has one value:
Use Set when you want to establish or replace a value.
This is the usual choice for headers such as:
AuthorizationContent-TypeUser-Agent
Add
Add appends another value.
The header now contains two values.
Whether multiple values are valid is determined by the HTTP semantics of that header, not by the fact that Go's Header type uses a slice.
Use Add only when multiple values are intentionally supported by the HTTP semantics of that header.
Common Mistake
Do not use Add when a value should be replaced:
If the code runs more than once against the same request, values accumulate.
Prefer:
Common Request Headers
Authorization
For Bearer-token authentication:
This value is sensitive; never log raw bearer tokens.
Accept
Accept describes the response formats the client is willing to receive:
Content-Type
Content-Type describes the request body:
The distinction is simple:
Do not use one in place of the other.
User-Agent
Identify the client application:
Use a meaningful application identifier when the remote service benefits from knowing what client is making the request.
Do not pretend to be a browser unless browser emulation is actually required.
Headers Belong to the Request
Headers that vary between operations should normally be attached to the http.Request.
This keeps request-specific state local to the request.
Avoid package-level mutable maps such as:
Shared mutable header state makes ownership and concurrency harder to reason about and can leak credentials between requests.
http.Client Has No Built-in Headers Field
http.Client has no general-purpose Headers field.
A client can be reused safely across many requests, while each request carries its own headers:
The separation is useful:
If every request made by a particular client needs the same header policy, a custom RoundTripper can centralize it.
Shared Header Policy with RoundTripper
For example, a transport can inject authentication into every request:
Create it with a known base transport:
The clone prevents the transport from modifying the caller's original request headers.
This pattern is useful for genuinely client-wide policy.
Do not use it simply to avoid writing three Header.Set calls. Per-request headers are usually clearer.
If the original request already contains an Authorization header, this transport overwrites it. That policy should be intentional.
Header Names Are Case-Insensitive
HTTP header field names are case-insensitive. Go canonicalizes header keys internally, so the casing used in a Set call does not create a different header.
These refer to the same header:
The second call replaces the first value.
When reading a header, use:
Do not depend on the capitalization used by the remote server.
Do Not Manually Set Content-Length
Avoid treating Content-Length like an ordinary application header:
Let net/http determine the request length from the body when possible.
For bodies whose size is known, http.NewRequest can determine the length automatically:
If you explicitly know the size and need to provide it, use the request field:
Do Not Reuse a Request Concurrently
A *http.Request represents one HTTP operation.
Do not send the same request concurrently:
Even if you do not mutate headers, the request body may not be replayable.
Create a separate request for each operation:
If you need to derive another request from an existing request, Clone (Go 1.18+) creates a separate request object and copies its headers:
A cloned request is separate from the original request, but cloning does not make an arbitrary request body independently reusable.
For streaming or replayable request bodies, body ownership requires separate consideration.
Never Log Sensitive Headers
This is dangerous:
Headers can contain credentials such as:
AuthorizationCookieX-API-Key
If headers must be logged for debugging, clone them and remove sensitive values:
Redaction should be part of the logging policy, not something added after a credential appears in production logs.
Production Example
A small API client can keep authentication and common request headers in one place while leaving request construction visible.
The important boundary is:
The helper does not need a custom header abstraction just to call Header.Set.
Common Mistakes
1. Using Add when you mean Set
2. Keeping per-request credentials in shared mutable state
Put them on the request, or enforce a deliberate client-wide policy via RoundTripper.
3. Confusing Accept and Content-Type
Accept describes the desired response.
Content-Type describes the request body.
4. Manually setting Content-Length
Let net/http manage it, or use Request.ContentLength when you explicitly know the size.
5. Logging raw headers
Redact authentication and other secrets first.
6. Sharing one *http.Request between concurrent operations
Create a new request for each operation.
Rule of Thumb
The core rule is simple:
Never log secrets. Do not manually write Content-Length. Do not share one Request between concurrent operations.