HTTP and Networking

Updated

July 30, 2026

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.