HTTP and Networking
Overview
Go’s net/http package provides a complete HTTP client and server implementation.
HTTP Client
// Simple GET
resp, err := http.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)Custom Request
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)With Context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)HTTP Server
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}JSON API
func userHandler(w http.ResponseWriter, r *http.Request) {
user := User{Name: "Alice"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}Middleware
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL)
next.ServeHTTP(w, r)
})
}
http.Handle("/", logging(http.HandlerFunc(handler)))ServeMux
mux := http.NewServeMux()
mux.HandleFunc("/users", usersHandler)
mux.HandleFunc("/posts", postsHandler)
http.ListenAndServe(":8080", mux)Static Files
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))Summary
| Function | Purpose |
|---|---|
http.Get() |
Simple GET request |
http.Post() |
Simple POST request |
http.NewRequest() |
Custom request |
http.HandleFunc() |
Register handler |
http.ListenAndServe() |
Start server |
Worked example
JSON POST API with middleware, tested via httptest.NewServer.
Save as main.go. Then:
go mod init example
go run .package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
)
type echoReq struct {
Msg string `json:"msg"`
}
type echoResp struct {
Echo string `json:"echo"`
}
func withJSON(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.Handle("POST /echo", withJSON(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req echoReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Msg == "" {
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "bad json"})
return
}
_ = json.NewEncoder(w).Encode(echoResp{Echo: strings.ToUpper(req.Msg)})
})))
srv := httptest.NewServer(mux)
defer srv.Close()
res, err := http.Post(srv.URL+"/echo", "application/json", strings.NewReader(`{"msg":"hi"}`))
if err != nil {
panic(err)
}
body, _ := io.ReadAll(res.Body)
res.Body.Close()
fmt.Println("status:", res.StatusCode)
fmt.Println("body:", strings.TrimSpace(string(body)))
}Expected output:
status: 200
body: {"echo":"HI"}
More examples
httptest.NewRecorder unit test of a pure handler (no listening socket).
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
func health(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
func main() {
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/health", nil)
health(rec, req)
fmt.Println(rec.Code, rec.Body.String())
}Expected output:
200 ok
Runnable example
Save as main.go. Then:
go mod init example
go run .package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"strings"
"time"
)
type User struct {
Name string `json:"name"`
}
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
func userHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(User{Name: "Alice"})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", userHandler)
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "ok")
})
// Middleware chain + real HTTP without ListenAndServe forever
srv := httptest.NewServer(logging(mux))
defer srv.Close()
// Client with timeout + context
client := &http.Client{Timeout: 2 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/users/42", nil)
if err != nil {
panic(err)
}
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("status:", resp.StatusCode)
fmt.Println("content-type:", resp.Header.Get("Content-Type"))
fmt.Println("body:", strings.TrimSpace(string(body)))
// httptest.NewRecorder for pure handler unit tests (no server)
rec := httptest.NewRecorder()
req2 := httptest.NewRequest(http.MethodGet, "/health", nil)
mux.ServeHTTP(rec, req2)
fmt.Println("health:", rec.Code, strings.TrimSpace(rec.Body.String()))
}Expected output: (log line may interleave; body lines are stable)
status: 200
content-type: application/json
body: {"name":"Alice"}
health: 200 ok
What to notice: httptest.NewServer is ideal for client+server integration examples; httptest.NewRecorder unit-tests handlers with zero network. Always set client timeouts and prefer NewRequestWithContext.
Try next: Add a POST /users that decodes JSON. Chain a second middleware that rejects missing Accept headers.