Example: HTTP GET
Use HTTP GET to retrieve a resource from an HTTP server.
A GET request normally reads a resource without modifying server-side state.
Quick Example
This is a minimal example.
Production code should normally return errors rather than printing them.
The basic flow is:
The important distinction is that err == nil does not mean the HTTP request succeeded at the application level.
Use Cases
- Fetch JSON from an HTTP API.
- Retrieve a small resource.
- Call a read-only HTTP endpoint.
For simple requests, http.Get is convenient.
For production code, an explicit http.Client is usually a better starting point.
Use an Explicit Timeout
http.Get uses http.DefaultClient.
The default client's Timeout is zero, meaning no overall timeout is set.
A remote server can therefore leave a request waiting indefinitely.
For request-specific cancellation, use a context:
These examples show timeout and cancellation only.
A production implementation should also check StatusCode, bound the response body when its size is not trusted, and handle the response body lifecycle correctly.
http.Client.Timeout is an overall request timeout. It continues to apply while the response body is being read.
This matters for a server that sends headers quickly but delivers the body very slowly.
err == nil Is Not a 2xx Response
A response such as:
is still a successful HTTP exchange from the transport's perspective.
The client may return:
with:
while:
Therefore, check the status separately:
Think of the two checks as different layers:
Always Close resp.Body
A successful request returns a response body that your code owns.
Close it:
Put the defer immediately after checking err.
http.Get does not close the response body for you.
The body should also normally be read to EOF when practical. If the body is closed before being fully consumed, the underlying connection may not be reusable for a subsequent request.
For an error response that you do not need to inspect, draining a small amount before closing can help when the response is small:
This is only a best effort. It does not guarantee connection reuse if the response body is larger than the drained amount.
Don't Blindly io.ReadAll
This is convenient:
It is also an unbounded memory operation.
For a small, trusted response, that may be exactly what you want.
For an untrusted or potentially large response, impose a limit.
A subtle mistake is:
This limits what is read, but it cannot tell whether:
- the response really ended, or
LimitReaderreached its artificial limit.
If exceeding the limit must be detected, read one extra byte:
This is an important interaction between net/http and io: the artificial EOF produced by LimitReader is indistinguishable from a real end of input to io.ReadAll.
ContentLength Is Not a Complete Size Check
ContentLength can reject an obviously oversized response before reading it:
But:
means the response length is unknown.
The body may be streamed, so ContentLength cannot replace a bounded read.
In practice, use both:
ContentLength provides an early rejection when the size is known.
LimitReader provides the actual memory bound when the size is unknown.
Retries Are a Separate Policy
GET is generally safe to retry because it is intended to be idempotent.
That does not mean every failed GET should be retried.
Typical candidates include:
- temporary network failures
429 Too Many Requests502 Bad Gateway503 Service Unavailable504 Gateway Timeout
A 429 response may also provide Retry-After, which can be used by the retry policy.
Most ordinary 4xx responses, such as 400, 401, 403, and 404, should not be retried automatically.
Retries should also respect the original request context and deadline.
Otherwise, each attempt can effectively create another full timeout window and turn a bounded operation into an unexpectedly long one.
Retry policy belongs above http.Client; http.Get does not provide it.
Redirects Are Followed by Default
Go's http.Client follows redirects by default.
The default policy follows up to 10 consecutive redirects.
For example:
A redirect chain can consume the request's entire timeout budget before the final response arrives.
Redirects can also cross trust boundaries.
If the request contains sensitive headers such as Authorization, review the redirect behavior instead of assuming those headers are sent unchanged to every destination.
Go's current net/http implementation does protect sensitive headers when redirecting to an unrelated domain, but redirect behavior is still an application policy decision.
Use CheckRedirect when the application needs explicit control:
This disables following the redirect and returns the most recent response.
There is no http.NoCheckRedirect variable in the Go standard library; CheckRedirect and http.ErrUseLastResponse are the standard mechanisms.
A Production-Oriented GET
The following combines the main concerns discussed above for a small response whose maximum size is known:
The client itself can be configured once and reused:
This is not a universal HTTP client template. For large responses, stream the body instead of using io.ReadAll. For retries, authentication, redirects, and protocol-specific status handling, add the policy required by the application.
Common Mistakes
1. No timeout
is fine for a minimal example.
It is often the wrong default for a service that depends on a remote server.
Use an explicit client when timeout behavior matters.
2. Treating HTTP errors as Go errors
does not detect HTTP 404 or 500.
Check StatusCode separately.
3. Forgetting Body.Close
The response body is still your responsibility.
4. Reading an unbounded body
can allocate according to the response size.
Use a limit when the response size is not trusted.
5. Assuming ContentLength is always available
ContentLength == -1 is valid.
A missing length does not mean the body is empty.
6. Adding retries without a deadline
Retries multiply waiting time.
Keep retries inside the request's overall time budget.
7. Assuming redirects are harmless
Redirects are requests too.
Review where they can go when the URL or request headers contain security-sensitive information.
Rule of Thumb
For a simple GET:
Then handle the layers separately:
- transport failure → check
err - HTTP failure → check
StatusCode - resource cleanup → close
Body - connection reuse → consume the body when practical
- untrusted response → bound the body
- known large response → reject with
ContentLengthearly - slow server → set a timeout
- repeated failures → define a retry policy
- redirects across trust boundaries → define a redirect policy
http.Get is easy to call.
The engineering work is deciding what happens around the call.