HTTP servers and clients

Go · Practical · 15 min

net/http is production-grade; many Go services use nothing else for HTTP.

net/http contains both a production-ready server and a client.

Server. A handler is anything implementing ServeHTTP(w http.ResponseWriter, r *http.Request); usually you register functions on a *http.ServeMux. Since Go 1.22 patterns include the method and wildcards:

  • mux.HandleFunc("GET /items/{id}", getItem) — read the wildcard with r.PathValue("id").
  • "POST /items" matches only POST; a mismatched method gets a 405 automatically.
  • "/static/" with a trailing slash matches the whole subtree; {path...} captures the rest.

In a handler, set headers with w.Header().Set(...) before w.WriteHeader(status) or the first Write. r.Context() is cancelled when the client disconnects. Each request runs in its own goroutine, so shared state needs synchronisation.

Middleware is just a function func(http.Handler) http.Handler wrapping another handler — for logging, auth or recovering panics.

Use an explicit http.Server with timeouts rather than http.ListenAndServe, which has none; slow clients could otherwise hold connections forever. For graceful shutdown, call srv.Shutdown(ctx) on SIGTERM.

Client. http.Get uses http.DefaultClient, which has no timeout. Create a client with &http.Client{Timeout: 10 * time.Second} and reuse it — it pools connections. Build requests with http.NewRequestWithContext, always defer resp.Body.Close(), and check resp.StatusCode: a 404 or 500 is not an error.

net/http/httptest lets you test handlers without a network (httptest.NewRecorder) or spin up a real test server.

Example

package main

import (
	"encoding/json"
	"log"
	"net/http"
	"time"
)

func getItem(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"id": id, "name": "widget"})
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /items/{id}", getItem)

	srv := &http.Server{
		Addr:         ":8080",
		Handler:      mux,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	log.Fatal(srv.ListenAndServe()) // curl localhost:8080/items/42
}

Remember

  • Go 1.22+ ServeMux patterns: "GET /items/{id}" + r.PathValue
  • Set server and client timeouts; the defaults have none
  • Always close resp.Body and check StatusCode yourself

Watch out for

  • Calling w.Header().Set after w.WriteHeader or Write — the header is already sent and the change is ignored.

Check yourself

  1. `resp, err := client.Get(url)` returns a 500 response. What is err?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pkg.go.dev/net/http

Builds on

Unlocks

Nothing builds on this yet.

HTTP servers and clients · Go · Topolearn