Go HTTP with the net/http Package
Go provides the net/http package for building HTTP clients and servers. It includes functions for sending requests, reading responses, registering request handlers, creating custom requests, and starting web servers.
This tutorial introduces the main net/http concepts used in Go programs, including GET requests, custom headers, response handling, timeouts, JSON requests, and a basic HTTP server.
Import the Go net/http Package
To import http package, include the following statement before using any of its methods in a Go program.
import "net/http"
Additional packages are often used with net/http. For example, io reads response bodies, encoding/json handles JSON data, and time helps configure request timeouts.
HTTP Request Methods Available in Go
An HTTP client sends a request method to describe the action it wants the server to perform. Common HTTP methods used in Go include:
- GET: retrieve a resource.
- HEAD: retrieve response headers without the response body.
- POST: submit data to a resource.
- PUT: replace or update a resource.
- PATCH: apply a partial update.
- DELETE: request removal of a resource.
The convenience functions http.Get(), http.Head(), http.Post(), and http.PostForm() cover common cases. Use http.NewRequest() or http.NewRequestWithContext() when you need a custom method, headers, body, or request context.
Send a Go HTTP GET Request
GET request is used to request data from a resource specified by the URI.
In the following example, we make a GET Request to https://www.tutorialkart.com. If there is no error in calling Get() function, we will print the response header.
example.go
package main
import "fmt"
import "net/http"
func main() {
resp, err := http.Get("https://www.tutorialkart.com/")
if err==nil {
fmt.Println(resp.Header)
}
resp.Body.Close()
}
Output
map[Content-Type:[text/html] Server:[Apache/2] Vary:[Accept-Encoding,User-Agent] X-Endurance-Cache-Level:[3] Date:[Thu, 11 Apr 2019 09:22:06 GMT] Keep-Alive:[timeout=30] Connection:[keep-alive] Last-Modified:[Fri, 05 Apr 2019 14:02:59 GMT] Etag:["7526-585c8f1c2e418-gzip"] Accept-Ranges:[bytes] Cache-Control:[max-age=10800] Expires:[Thu, 11 Apr 2019 12:22:06 GMT]]
Note that, when we are done with the response object, close this response object using resp.Body.Close() function.
The existing example calls resp.Body.Close() even when http.Get() returns an error. In production code, return after the error check and defer the close only after confirming that resp is valid.
Read the HTTP Response Body Safely
A successful request returns an *http.Response. The response contains the status code, headers, protocol information, and a body stream. Close the body after the request has completed so that the transport can release or reuse the underlying connection.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://www.tutorialkart.com/")
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("could not read response body:", err)
return
}
fmt.Println("status:", resp.Status)
fmt.Println("content type:", resp.Header.Get("Content-Type"))
fmt.Println("body length:", len(body))
}
The complete body is loaded into memory by io.ReadAll(). For a large download or streaming response, process resp.Body incrementally instead of reading it all at once.
Check Go HTTP Status Codes Before Processing Data
A nil error from http.Get() means that the request was sent and an HTTP response was received. It does not guarantee a successful status such as 200 OK. A server can return 404 Not Found, 500 Internal Server Error, or another non-2xx response without causing a transport error.
resp, err := http.Get("https://www.tutorialkart.com/")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("unexpected HTTP status: %s", resp.Status)
}
Applications should decide which status codes are acceptable for each endpoint. For example, a redirect, cache response, or partial-content response may be valid in one application but not another.
Create a Go HTTP Request with NewRequest
Use http.NewRequest() when the request needs custom headers, a request body, or a method other than the convenience functions provide.
package main
import (
"fmt"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://www.tutorialkart.com/", nil)
if err != nil {
fmt.Println("could not create request:", err)
return
}
req.Header.Set("Accept", "text/html")
req.Header.Set("User-Agent", "Go-http-example/1.0")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
http.DefaultClient.Do() sends the prepared request. Header names are case-insensitive, and Go stores them in the request’s Header map.
Configure an HTTP Client Timeout in Go
The default HTTP client does not set an overall request timeout. For application code, create and reuse an http.Client with a timeout appropriate for the service being called.
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Get("https://www.tutorialkart.com/")
if err != nil {
fmt.Println("request failed or timed out:", err)
return
}
defer resp.Body.Close()
A timeout prevents a request from waiting indefinitely for connection setup, redirects, server processing, or response-body transfer. Reuse the same client instead of constructing a new client for every request.
Cancel a Go HTTP Request with Context
A context can cancel a request when a deadline expires or when the caller no longer needs the result. Use http.NewRequestWithContext() to attach the context to the request.
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://www.tutorialkart.com/",
nil,
)
if err != nil {
fmt.Println("could not create request:", err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
Request contexts are especially useful in servers, where an outbound request should be canceled when the incoming client disconnects or the handler deadline expires.
Send JSON with a Go HTTP POST Request
To send JSON, encode the value, place it in the request body, and set the Content-Type header to application/json.
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
payload := User{Name: "Alex", Age: 28}
data, err := json.Marshal(payload)
if err != nil {
fmt.Println("could not encode JSON:", err)
return
}
req, err := http.NewRequest(
http.MethodPost,
"https://example.com/users",
bytes.NewReader(data),
)
if err != nil {
fmt.Println("could not create request:", err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
Replace the example URL with an endpoint that accepts the posted JSON structure. The request body should match the schema expected by that endpoint.
Decode a JSON HTTP Response in Go
Use json.NewDecoder() to decode a JSON response directly from resp.Body. The fields and JSON tags in the destination structure must match the response format.
type APIResponse struct {
Message string `json:"message"`
ID int `json:"id"`
}
var result APIResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
fmt.Println("could not decode response:", err)
return
}
fmt.Println(result.Message, result.ID)
Check the response status and content type before assuming that the body contains the expected JSON. Some services return an HTML or plain-text error body for failed requests.
Build a Basic Go HTTP Server
The same net/http package can run an HTTP server. Register a handler for a URL path and call http.ListenAndServe() with the address on which the server should listen.
package main
import (
"fmt"
"log"
"net/http"
)
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintln(w, "Hello from Go HTTP server")
}
func main() {
http.HandleFunc("/", homeHandler)
log.Println("server listening on http://localhost:8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
Run the program and open http://localhost:8080 in a browser or request it from a terminal.
curl http://localhost:8080/
Output
Hello from Go HTTP server
Use a Custom ServeMux for Go HTTP Routes
A http.ServeMux routes incoming requests to handlers. Using a custom multiplexer avoids registering application routes on the package-level default multiplexer.
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `{"status":"ok"}`)
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
The custom server value can also define read, write, idle, and header timeouts instead of relying entirely on package defaults.
Configure Timeouts for a Go HTTP Server
For a network-facing server, configure time limits so that slow or stalled connections do not occupy resources indefinitely.
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
The exact timeout values depend on the type of traffic, request size, streaming behavior, and deployment environment.
Go HTTP Client and Server Types at a Glance
| Go HTTP type or function | Purpose |
|---|---|
http.Client | Sends HTTP requests and controls client-level behavior such as timeouts and redirects. |
http.Request | Represents an outgoing client request or incoming server request. |
http.Response | Contains the status, headers, and body returned by a server. |
http.Handler | Processes an incoming server request and writes a response. |
http.ResponseWriter | Lets a handler set headers, status codes, and response-body data. |
http.ServeMux | Matches request paths to HTTP handlers. |
http.Server | Configures and runs an HTTP server. |
http.NewRequestWithContext() | Creates a request with cancellation and deadline support. |
Common Go net/http Mistakes
- Closing a nil response: call
defer resp.Body.Close()only after confirming that the request returned no error. - Ignoring status codes: check
resp.StatusCodebefore decoding or processing the response body. - Using no timeout: configure an
http.Clienttimeout or request context for external calls. - Creating a client per request: reuse clients so their transports and connections can also be reused.
- Reading unbounded bodies: avoid loading an unknown or very large response entirely into memory.
- Writing the body before setting headers: set response headers and status before calling
Write()or functions that write to the response. - Assuming every response is JSON: verify the status and content type before decoding.
Frequently Asked Questions About Go HTTP
What does the net/http package do in Go?
The net/http package provides HTTP client and server implementations. It can send requests, read responses, register handlers, route paths, serve files, manage headers, and run HTTP servers.
How do I make an HTTP request in Go?
Use a convenience function such as http.Get() for a simple request. Use http.NewRequest() or http.NewRequestWithContext() with client.Do() when you need custom headers, methods, bodies, deadlines, or cancellation.
Why must resp.Body be closed in Go?
The response body holds the stream associated with the HTTP response. Closing it releases resources and can allow the HTTP transport to reuse the connection when the body has been handled appropriately.
What is the difference between http.Get and http.NewRequest?
http.Get() creates and sends a basic GET request immediately. http.NewRequest() only constructs a request, allowing the program to set headers, choose another method, attach a body, and then send it with an HTTP client.
How do I start an HTTP server in Go?
Register one or more handlers with a http.ServeMux, create an http.Server, and call ListenAndServe(). A minimal program can also use http.HandleFunc() and http.ListenAndServe() directly.
Editorial QA Checklist for Go HTTP Examples
- Verify that every successful client request closes
resp.Bodyafter the error check. - Confirm that client examples check both transport errors and HTTP status codes.
- Check that JSON requests set the correct
Content-Typeand use structures matching the documented payload. - Ensure reusable HTTP clients and servers have suitable timeout settings where required.
- Confirm that server handlers set headers before writing the response body.
- Run local server examples and verify that the shown route, port, command, and output agree.
- Check that all example URLs are placeholders or existing links already present in the tutorial.
Summary of Go HTTP Clients and Servers
Use the net/http package to create both HTTP clients and servers in Go. For clients, check request errors, inspect status codes, close response bodies, reuse configured clients, and set timeouts. For servers, define handlers, route requests with a multiplexer, return appropriate headers and status codes, and configure server timeouts.
In this Go Tutorial, we learned about Go HTTP package, with example programs.
TutorialKart.com