Web Development

Why Web Development Matters

When building a modern web application that needs to serve millions of users, process real-time data, and maintain 99.9% uptime, your choice of web framework and architecture determines whether your application can scale, handle concurrent users efficiently, and respond quickly under load. Go's web development capabilities are designed for exactly these challenges.

Real-World Impact:

  • Netflix streams billions of hours of content using Go HTTP servers
  • Uber handles millions of ride requests per day with Go microservices
  • Dropbox serves petabytes of file storage through Go APIs
  • Cloudflare processes billions of HTTP requests daily using Go

Why Go Excels at Web Development:

  • Millions of Connections: Single Go server can handle millions of concurrent connections
  • Sub-millisecond Latency: Compiled performance + efficient goroutine scheduling
  • Minimal Memory: ~2KB per connection vs ~100KB in Node.js
  • Built-in HTTP/2: No external dependencies for modern protocols
  • Production Ready: Battle-tested at Google scale for over a decade

Learning Objectives

After completing this article, you will be able to:

Build HTTP Servers - Create robust servers from scratch using only standard library
Handle High Concurrency - Serve thousands of simultaneous connections efficiently
Design REST APIs - Build scalable APIs with proper routing and middleware
Process Forms & JSON - Handle user input and data serialization
Implement Security - Add authentication, CORS, and security headers
Master Performance - Optimize for throughput and low latency
Deploy with Confidence - Understand production patterns and deployment strategies

Core Concepts - Go's Web Architecture

Go approaches web development differently from other languages. Understanding this foundation is crucial for building efficient applications.

The Goroutine Advantage

Unlike traditional thread-per-request models, Go uses lightweight goroutines:

Traditional Model:
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│ Thread 1    │    │ Thread 2    │    │ Thread 3    │  ← Heavy, ~1MB each
│ 2KB Stack   │    │ 2KB Stack   │    │ 2KB Stack   │
└─────────────┘    └─────────────┘    └─────────────┘

Go Model:
┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐
│ Go 1 │  │ Go 2 │  │ Go 3 │  │ Go 4 │  │ Go 5 │  ← Lightweight, ~2KB each
│2KB Stk│  │2KB Stk│  │2KB Stk│  │2KB Stk│  │2KB Stk│
└──────┘  └──────┘  └──────┘  └──────┘  └──────┘

Result: 1000x more concurrent connections with same memory!

Key Package Architecture

Go's web development centers around net/http with complementary packages:

Package Role Key Components
net/http Core HTTP Server, Client, Handlers, ResponseWriter
context Request lifecycle Cancellation, timeouts, values
html/template Server templates Safe HTML generation, template inheritance
mime Content types Detecting file types, setting headers
crypto/tls HTTPS TLS certificates, secure connections

Request Processing Flow

1. Client sends HTTP request
       ↓
2. Connection accepted
       ↓
3. Router matches URL pattern
       ↓
4. Middleware chain executes
       ↓
5. Handler processes request
       ↓
6. Response written and sent
       ↓
7. Connection closed or kept alive

Understanding HTTP Handlers

Every HTTP server in Go is built on the concept of handlers - functions that process requests and generate responses.

The Handler Interface

At the core of Go's HTTP package is the http.Handler interface:

1type Handler interface {
2    ServeHTTP(ResponseWriter, *Request)
3}

Two Ways to Create Handlers:

  1. Handler Functions - Simple functions for basic routes
  2. Handler Types - Custom types implementing ServeHTTP for stateful handlers

Handler Functions vs Handler Types

 1// run
 2package main
 3
 4import (
 5    "fmt"
 6    "net/http"
 7    "sync/atomic"
 8)
 9
10// Pattern 1: Handler Function - Stateless, simple
11func simpleHandler(w http.ResponseWriter, r *http.Request) {
12    fmt.Fprintf(w, "Hello from handler function!\n")
13}
14
15// Pattern 2: Handler Type - Stateful, reusable
16type CounterHandler struct {
17    count atomic.Int64
18}
19
20func (h *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
21    count := h.count.Add(1)
22    fmt.Fprintf(w, "Request count: %d\n", count)
23}
24
25func main() {
26    // Register handler function
27    http.HandleFunc("/simple", simpleHandler)
28
29    // Register handler type
30    counter := &CounterHandler{}
31    http.Handle("/counter", counter)
32
33    fmt.Println("Server with both handler patterns on :8080...")
34    http.ListenAndServe(":8080", nil)
35}

When to Use Each Pattern:

Pattern Use Case Example
Handler Function Stateless operations Static pages, simple APIs
Handler Type Stateful operations Request counting, connection pooling

HTTP Request Anatomy

Understanding the *http.Request structure is essential for processing web requests effectively.

Request Components Breakdown

 1// run
 2package main
 3
 4import (
 5    "fmt"
 6    "io"
 7    "net/http"
 8    "strings"
 9)
10
11func requestAnalyzer(w http.ResponseWriter, r *http.Request) {
12    w.Header().Set("Content-Type", "text/plain")
13
14    fmt.Fprintf(w, "Request Analysis\n")
15    fmt.Fprintf(w, "================\n\n")
16
17    // HTTP Method
18    fmt.Fprintf(w, "Method: %s\n", r.Method)
19
20    // URL Components
21    fmt.Fprintf(w, "\nURL Analysis:\n")
22    fmt.Fprintf(w, "  Full URL: %s\n", r.URL.String())
23    fmt.Fprintf(w, "  Path: %s\n", r.URL.Path)
24    fmt.Fprintf(w, "  Query String: %s\n", r.URL.RawQuery)
25
26    // Query Parameters
27    if len(r.URL.Query()) > 0 {
28        fmt.Fprintf(w, "\nQuery Parameters:\n")
29        for key, values := range r.URL.Query() {
30            for _, value := range values {
31                fmt.Fprintf(w, "  %s = %s\n", key, value)
32            }
33        }
34    }
35
36    // Headers
37    fmt.Fprintf(w, "\nHeaders:\n")
38    for name, values := range r.Header {
39        for _, value := range values {
40            fmt.Fprintf(w, "  %s: %s\n", name, value)
41        }
42    }
43
44    // Body (for POST/PUT requests)
45    if r.Method == "POST" || r.Method == "PUT" {
46        body, err := io.ReadAll(r.Body)
47        if err == nil && len(body) > 0 {
48            fmt.Fprintf(w, "\nRequest Body:\n")
49            fmt.Fprintf(w, "  Length: %d bytes\n", len(body))
50            fmt.Fprintf(w, "  Content: %s\n", string(body))
51        }
52    }
53
54    // Client Information
55    fmt.Fprintf(w, "\nClient Info:\n")
56    fmt.Fprintf(w, "  Remote Address: %s\n", r.RemoteAddr)
57    fmt.Fprintf(w, "  Protocol: %s\n", r.Proto)
58
59    // Cookies
60    if len(r.Cookies()) > 0 {
61        fmt.Fprintf(w, "\nCookies:\n")
62        for _, cookie := range r.Cookies() {
63            fmt.Fprintf(w, "  %s = %s\n", cookie.Name, cookie.Value)
64        }
65    }
66}
67
68func main() {
69    http.HandleFunc("/analyze", requestAnalyzer)
70
71    fmt.Println("Request Analyzer Server on :8080")
72    fmt.Println("Try: curl 'http://localhost:8080/analyze?user=alice&age=30' -H 'X-Custom: value'")
73    http.ListenAndServe(":8080", nil)
74}

Reading Request Bodies Safely

Different content types require different handling approaches:

  1// run
  2package main
  3
  4import (
  5    "encoding/json"
  6    "fmt"
  7    "io"
  8    "net/http"
  9    "net/url"
 10)
 11
 12type User struct {
 13    Name  string `json:"name"`
 14    Email string `json:"email"`
 15    Age   int    `json:"age"`
 16}
 17
 18func handleJSON(w http.ResponseWriter, r *http.Request) {
 19    if r.Method != "POST" {
 20        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
 21        return
 22    }
 23
 24    // Check Content-Type
 25    if r.Header.Get("Content-Type") != "application/json" {
 26        http.Error(w, "Content-Type must be application/json", http.StatusBadRequest)
 27        return
 28    }
 29
 30    // Limit request body size (10MB max)
 31    r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
 32
 33    var user User
 34    decoder := json.NewDecoder(r.Body)
 35    decoder.DisallowUnknownFields() // Reject unknown fields
 36
 37    if err := decoder.Decode(&user); err != nil {
 38        http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
 39        return
 40    }
 41
 42    // Validate data
 43    if user.Name == "" || user.Email == "" {
 44        http.Error(w, "Name and email are required", http.StatusBadRequest)
 45        return
 46    }
 47
 48    // Process user...
 49    w.Header().Set("Content-Type", "application/json")
 50    json.NewEncoder(w).Encode(map[string]interface{}{
 51        "message": "User created successfully",
 52        "user":    user,
 53    })
 54}
 55
 56func handleForm(w http.ResponseWriter, r *http.Request) {
 57    if r.Method != "POST" {
 58        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
 59        return
 60    }
 61
 62    // Parse form data (automatically handles application/x-www-form-urlencoded)
 63    if err := r.ParseForm(); err != nil {
 64        http.Error(w, "Failed to parse form", http.StatusBadRequest)
 65        return
 66    }
 67
 68    name := r.FormValue("name")
 69    email := r.FormValue("email")
 70
 71    if name == "" || email == "" {
 72        http.Error(w, "Name and email are required", http.StatusBadRequest)
 73        return
 74    }
 75
 76    fmt.Fprintf(w, "Form submitted: name=%s, email=%s\n", name, email)
 77}
 78
 79func handleMultipart(w http.ResponseWriter, r *http.Request) {
 80    if r.Method != "POST" {
 81        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
 82        return
 83    }
 84
 85    // Parse multipart form (for file uploads)
 86    // Limit to 32MB in memory, rest spills to disk
 87    if err := r.ParseMultipartForm(32 << 20); err != nil {
 88        http.Error(w, "Failed to parse multipart form", http.StatusBadRequest)
 89        return
 90    }
 91
 92    // Access form fields
 93    name := r.FormValue("name")
 94
 95    // Access uploaded files
 96    file, header, err := r.FormFile("file")
 97    if err != nil {
 98        http.Error(w, "File upload failed", http.StatusBadRequest)
 99        return
100    }
101    defer file.Close()
102
103    // Read file content
104    content, _ := io.ReadAll(file)
105
106    fmt.Fprintf(w, "File uploaded: %s (%d bytes)\n", header.Filename, len(content))
107    fmt.Fprintf(w, "Name field: %s\n", name)
108}
109
110func main() {
111    http.HandleFunc("/api/users", handleJSON)
112    http.HandleFunc("/form", handleForm)
113    http.HandleFunc("/upload", handleMultipart)
114
115    fmt.Println("Body Parser Server on :8080")
116    fmt.Println("Endpoints:")
117    fmt.Println("  POST /api/users - JSON payload")
118    fmt.Println("  POST /form      - Form data")
119    fmt.Println("  POST /upload    - Multipart file upload")
120    http.ListenAndServe(":8080", nil)
121}

HTTP Response Patterns

The http.ResponseWriter interface provides methods for building responses. Understanding response patterns is crucial for API design.

Response Status Codes

  1// run
  2package main
  3
  4import (
  5    "encoding/json"
  6    "fmt"
  7    "net/http"
  8)
  9
 10type ErrorResponse struct {
 11    Error   string `json:"error"`
 12    Code    int    `json:"code"`
 13    Message string `json:"message"`
 14}
 15
 16func sendJSON(w http.ResponseWriter, status int, data interface{}) {
 17    w.Header().Set("Content-Type", "application/json")
 18    w.WriteHeader(status)
 19    json.NewEncoder(w).Encode(data)
 20}
 21
 22func sendError(w http.ResponseWriter, status int, message string) {
 23    sendJSON(w, status, ErrorResponse{
 24        Error:   http.StatusText(status),
 25        Code:    status,
 26        Message: message,
 27    })
 28}
 29
 30// Success Responses
 31func handleSuccess(w http.ResponseWriter, r *http.Request) {
 32    // 200 OK - Standard success response
 33    sendJSON(w, http.StatusOK, map[string]string{
 34        "message": "Operation successful",
 35    })
 36}
 37
 38func handleCreated(w http.ResponseWriter, r *http.Request) {
 39    // 201 Created - Resource created successfully
 40    w.Header().Set("Location", "/api/users/123")
 41    sendJSON(w, http.StatusCreated, map[string]interface{}{
 42        "id":      123,
 43        "message": "User created",
 44    })
 45}
 46
 47func handleNoContent(w http.ResponseWriter, r *http.Request) {
 48    // 204 No Content - Success with no response body
 49    w.WriteHeader(http.StatusNoContent)
 50}
 51
 52// Client Error Responses
 53func handleBadRequest(w http.ResponseWriter, r *http.Request) {
 54    // 400 Bad Request - Invalid client input
 55    sendError(w, http.StatusBadRequest, "Invalid request format")
 56}
 57
 58func handleUnauthorized(w http.ResponseWriter, r *http.Request) {
 59    // 401 Unauthorized - Authentication required
 60    w.Header().Set("WWW-Authenticate", "Bearer")
 61    sendError(w, http.StatusUnauthorized, "Authentication credentials required")
 62}
 63
 64func handleForbidden(w http.ResponseWriter, r *http.Request) {
 65    // 403 Forbidden - Authenticated but not authorized
 66    sendError(w, http.StatusForbidden, "You don't have permission to access this resource")
 67}
 68
 69func handleNotFound(w http.ResponseWriter, r *http.Request) {
 70    // 404 Not Found - Resource doesn't exist
 71    sendError(w, http.StatusNotFound, "The requested resource was not found")
 72}
 73
 74// Server Error Responses
 75func handleServerError(w http.ResponseWriter, r *http.Request) {
 76    // 500 Internal Server Error - Unexpected server problem
 77    sendError(w, http.StatusInternalServerError, "An unexpected error occurred")
 78}
 79
 80func handleServiceUnavailable(w http.ResponseWriter, r *http.Request) {
 81    // 503 Service Unavailable - Temporary unavailability
 82    w.Header().Set("Retry-After", "60")
 83    sendError(w, http.StatusServiceUnavailable, "Service temporarily unavailable")
 84}
 85
 86func main() {
 87    // Success responses
 88    http.HandleFunc("/success", handleSuccess)
 89    http.HandleFunc("/created", handleCreated)
 90    http.HandleFunc("/nocontent", handleNoContent)
 91
 92    // Client errors
 93    http.HandleFunc("/badrequest", handleBadRequest)
 94    http.HandleFunc("/unauthorized", handleUnauthorized)
 95    http.HandleFunc("/forbidden", handleForbidden)
 96    http.HandleFunc("/notfound", handleNotFound)
 97
 98    // Server errors
 99    http.HandleFunc("/error", handleServerError)
100    http.HandleFunc("/unavailable", handleServiceUnavailable)
101
102    fmt.Println("HTTP Status Code Examples on :8080")
103    http.ListenAndServe(":8080", nil)
104}

Response Headers and Content Types

 1// run
 2package main
 3
 4import (
 5    "encoding/json"
 6    "encoding/xml"
 7    "fmt"
 8    "net/http"
 9)
10
11type Product struct {
12    XMLName xml.Name `xml:"product" json:"-"`
13    ID      int      `xml:"id" json:"id"`
14    Name    string   `xml:"name" json:"name"`
15    Price   float64  `xml:"price" json:"price"`
16}
17
18func negotiateContent(w http.ResponseWriter, r *http.Request) {
19    product := Product{
20        ID:    1,
21        Name:  "Laptop",
22        Price: 999.99,
23    }
24
25    // Content negotiation based on Accept header
26    accept := r.Header.Get("Accept")
27
28    switch accept {
29    case "application/xml":
30        w.Header().Set("Content-Type", "application/xml")
31        w.WriteHeader(http.StatusOK)
32        xml.NewEncoder(w).Encode(product)
33
34    case "application/json":
35        fallthrough
36    default:
37        w.Header().Set("Content-Type", "application/json")
38        w.WriteHeader(http.StatusOK)
39        json.NewEncoder(w).Encode(product)
40    }
41}
42
43func setCachingHeaders(w http.ResponseWriter, r *http.Request) {
44    // Cache static resources aggressively
45    w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
46    w.Header().Set("Expires", "Wed, 01 Jan 2025 00:00:00 GMT")
47
48    fmt.Fprintf(w, "This response can be cached for 1 year")
49}
50
51func setNoCacheHeaders(w http.ResponseWriter, r *http.Request) {
52    // Don't cache dynamic content
53    w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
54    w.Header().Set("Pragma", "no-cache")
55    w.Header().Set("Expires", "0")
56
57    fmt.Fprintf(w, "This response should never be cached")
58}
59
60func setCORSHeaders(w http.ResponseWriter, r *http.Request) {
61    // Enable cross-origin requests
62    w.Header().Set("Access-Control-Allow-Origin", "*")
63    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
64    w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
65    w.Header().Set("Access-Control-Max-Age", "3600")
66
67    if r.Method == "OPTIONS" {
68        w.WriteHeader(http.StatusOK)
69        return
70    }
71
72    fmt.Fprintf(w, "CORS headers set for cross-origin access")
73}
74
75func setSecurityHeaders(w http.ResponseWriter, r *http.Request) {
76    // Security best practices
77    w.Header().Set("X-Content-Type-Options", "nosniff")
78    w.Header().Set("X-Frame-Options", "DENY")
79    w.Header().Set("X-XSS-Protection", "1; mode=block")
80    w.Header().Set("Content-Security-Policy", "default-src 'self'")
81    w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
82
83    fmt.Fprintf(w, "Security headers configured")
84}
85
86func main() {
87    http.HandleFunc("/product", negotiateContent)
88    http.HandleFunc("/cache", setCachingHeaders)
89    http.HandleFunc("/nocache", setNoCacheHeaders)
90    http.HandleFunc("/cors", setCORSHeaders)
91    http.HandleFunc("/security", setSecurityHeaders)
92
93    fmt.Println("Response Headers Demo on :8080")
94    fmt.Println("Try different Accept headers:")
95    fmt.Println("  curl -H 'Accept: application/json' http://localhost:8080/product")
96    fmt.Println("  curl -H 'Accept: application/xml' http://localhost:8080/product")
97    http.ListenAndServe(":8080", nil)
98}

Advanced Routing Patterns

The standard library's http.ServeMux provides basic routing. For production applications, understanding routing patterns is essential.

URL Pattern Matching

 1// run
 2package main
 3
 4import (
 5    "fmt"
 6    "net/http"
 7    "strings"
 8)
 9
10// Custom router with parameter extraction
11type Router struct {
12    routes map[string]http.HandlerFunc
13}
14
15func NewRouter() *Router {
16    return &Router{
17        routes: make(map[string]http.HandlerFunc),
18    }
19}
20
21func (router *Router) Handle(pattern string, handler http.HandlerFunc) {
22    router.routes[pattern] = handler
23}
24
25func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
26    path := r.URL.Path
27
28    // Exact match first
29    if handler, ok := router.routes[path]; ok {
30        handler(w, r)
31        return
32    }
33
34    // Pattern matching with parameters
35    for pattern, handler := range router.routes {
36        if params := matchPattern(pattern, path); params != nil {
37            // Store params in context (simplified here)
38            handler(w, r)
39            return
40        }
41    }
42
43    http.NotFound(w, r)
44}
45
46func matchPattern(pattern, path string) map[string]string {
47    patternParts := strings.Split(strings.Trim(pattern, "/"), "/")
48    pathParts := strings.Split(strings.Trim(path, "/"), "/")
49
50    if len(patternParts) != len(pathParts) {
51        return nil
52    }
53
54    params := make(map[string]string)
55
56    for i, part := range patternParts {
57        if strings.HasPrefix(part, ":") {
58            // This is a parameter
59            paramName := part[1:]
60            params[paramName] = pathParts[i]
61        } else if part != pathParts[i] {
62            // Not a match
63            return nil
64        }
65    }
66
67    return params
68}
69
70func main() {
71    router := NewRouter()
72
73    // Static routes
74    router.Handle("/", func(w http.ResponseWriter, r *http.Request) {
75        fmt.Fprintf(w, "Home page\n")
76    })
77
78    router.Handle("/about", func(w http.ResponseWriter, r *http.Request) {
79        fmt.Fprintf(w, "About page\n")
80    })
81
82    // Dynamic routes (pattern matching)
83    router.Handle("/users/:id", func(w http.ResponseWriter, r *http.Request) {
84        // In a real implementation, extract ID from context
85        fmt.Fprintf(w, "User profile\n")
86    })
87
88    router.Handle("/posts/:id/comments/:commentId", func(w http.ResponseWriter, r *http.Request) {
89        fmt.Fprintf(w, "Post comment detail\n")
90    })
91
92    fmt.Println("Custom Router on :8080")
93    fmt.Println("Try:")
94    fmt.Println("  /")
95    fmt.Println("  /about")
96    fmt.Println("  /users/123")
97    fmt.Println("  /posts/456/comments/789")
98    http.ListenAndServe(":8080", router)
99}

Method-Based Routing

  1// run
  2package main
  3
  4import (
  5    "encoding/json"
  6    "fmt"
  7    "net/http"
  8)
  9
 10// MethodRouter routes based on HTTP method
 11type MethodRouter struct {
 12    handlers map[string]http.HandlerFunc
 13}
 14
 15func NewMethodRouter() *MethodRouter {
 16    return &MethodRouter{
 17        handlers: make(map[string]http.HandlerFunc),
 18    }
 19}
 20
 21func (mr *MethodRouter) GET(handler http.HandlerFunc) {
 22    mr.handlers["GET"] = handler
 23}
 24
 25func (mr *MethodRouter) POST(handler http.HandlerFunc) {
 26    mr.handlers["POST"] = handler
 27}
 28
 29func (mr *MethodRouter) PUT(handler http.HandlerFunc) {
 30    mr.handlers["PUT"] = handler
 31}
 32
 33func (mr *MethodRouter) DELETE(handler http.HandlerFunc) {
 34    mr.handlers["DELETE"] = handler
 35}
 36
 37func (mr *MethodRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 38    handler, ok := mr.handlers[r.Method]
 39    if !ok {
 40        w.Header().Set("Allow", mr.allowedMethods())
 41        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
 42        return
 43    }
 44
 45    handler(w, r)
 46}
 47
 48func (mr *MethodRouter) allowedMethods() string {
 49    methods := ""
 50    for method := range mr.handlers {
 51        if methods != "" {
 52            methods += ", "
 53        }
 54        methods += method
 55    }
 56    return methods
 57}
 58
 59func main() {
 60    // RESTful user endpoint with method routing
 61    userRouter := NewMethodRouter()
 62
 63    userRouter.GET(func(w http.ResponseWriter, r *http.Request) {
 64        users := []map[string]interface{}{
 65            {"id": 1, "name": "Alice"},
 66            {"id": 2, "name": "Bob"},
 67        }
 68        w.Header().Set("Content-Type", "application/json")
 69        json.NewEncoder(w).Encode(users)
 70    })
 71
 72    userRouter.POST(func(w http.ResponseWriter, r *http.Request) {
 73        var user map[string]interface{}
 74        json.NewDecoder(r.Body).Decode(&user)
 75        user["id"] = 3
 76
 77        w.Header().Set("Content-Type", "application/json")
 78        w.WriteHeader(http.StatusCreated)
 79        json.NewEncoder(w).Encode(user)
 80    })
 81
 82    userRouter.PUT(func(w http.ResponseWriter, r *http.Request) {
 83        var user map[string]interface{}
 84        json.NewDecoder(r.Body).Decode(&user)
 85
 86        w.Header().Set("Content-Type", "application/json")
 87        json.NewEncoder(w).Encode(user)
 88    })
 89
 90    userRouter.DELETE(func(w http.ResponseWriter, r *http.Request) {
 91        w.WriteHeader(http.StatusNoContent)
 92    })
 93
 94    http.Handle("/api/users", userRouter)
 95
 96    fmt.Println("Method Router on :8080")
 97    fmt.Println("Try:")
 98    fmt.Println("  GET    /api/users - List users")
 99    fmt.Println("  POST   /api/users - Create user")
100    fmt.Println("  PUT    /api/users - Update user")
101    fmt.Println("  DELETE /api/users - Delete user")
102    http.ListenAndServe(":8080", nil)
103}

Server Configuration and Timeouts

Production HTTP servers require proper timeout configuration to prevent resource exhaustion.

Comprehensive Server Configuration

 1// run
 2package main
 3
 4import (
 5    "context"
 6    "fmt"
 7    "log"
 8    "net/http"
 9    "os"
10    "os/signal"
11    "syscall"
12    "time"
13)
14
15func createProductionServer() *http.Server {
16    mux := http.NewServeMux()
17
18    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
19        fmt.Fprintf(w, "Production server responding\n")
20    })
21
22    mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
23        // Simulate slow operation
24        time.Sleep(3 * time.Second)
25        fmt.Fprintf(w, "Slow operation completed\n")
26    })
27
28    server := &http.Server{
29        Addr:    ":8080",
30        Handler: mux,
31
32        // Timeouts prevent resource exhaustion
33        ReadTimeout:       10 * time.Second, // Time to read request headers + body
34        ReadHeaderTimeout: 5 * time.Second,  // Time to read request headers
35        WriteTimeout:      10 * time.Second, // Time to write response
36        IdleTimeout:       120 * time.Second, // Time before closing idle connections
37
38        // Limits
39        MaxHeaderBytes: 1 << 20, // 1 MB max header size
40
41        // Error logging
42        ErrorLog: log.New(os.Stderr, "HTTP: ", log.LstdFlags),
43    }
44
45    return server
46}
47
48func main() {
49    server := createProductionServer()
50
51    // Start server in goroutine
52    go func() {
53        fmt.Printf("Production server starting on %s\n", server.Addr)
54        fmt.Println("Timeout configuration:")
55        fmt.Println("  ReadTimeout:       10s")
56        fmt.Println("  WriteTimeout:      10s")
57        fmt.Println("  IdleTimeout:       120s")
58        fmt.Println("  ReadHeaderTimeout: 5s")
59
60        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
61            log.Fatalf("Server failed: %v", err)
62        }
63    }()
64
65    // Graceful shutdown
66    quit := make(chan os.Signal, 1)
67    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
68    <-quit
69
70    fmt.Println("\nShutting down server...")
71
72    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
73    defer cancel()
74
75    if err := server.Shutdown(ctx); err != nil {
76        log.Fatalf("Server forced to shutdown: %v", err)
77    }
78
79    fmt.Println("Server gracefully stopped")
80}

Connection Pooling for HTTP Clients

 1// run
 2package main
 3
 4import (
 5    "fmt"
 6    "io"
 7    "net"
 8    "net/http"
 9    "time"
10)
11
12func createOptimizedClient() *http.Client {
13    transport := &http.Transport{
14        // Connection pooling
15        MaxIdleConns:        100,              // Maximum idle connections across all hosts
16        MaxIdleConnsPerHost: 10,               // Maximum idle connections per host
17        MaxConnsPerHost:     100,              // Maximum connections per host
18
19        // Timeouts
20        IdleConnTimeout:       90 * time.Second,  // Time before closing idle connections
21        TLSHandshakeTimeout:   10 * time.Second,  // TLS handshake timeout
22        ResponseHeaderTimeout: 10 * time.Second,  // Time to receive response headers
23        ExpectContinueTimeout: 1 * time.Second,   // Time to wait for 100-Continue
24
25        // Connection settings
26        DisableKeepAlives:   false,            // Enable keep-alive
27        DisableCompression:  false,            // Enable gzip compression
28        ForceAttemptHTTP2:   true,             // Try HTTP/2
29
30        // Dialer configuration
31        DialContext: (&net.Dialer{
32            Timeout:   30 * time.Second,       // Connection timeout
33            KeepAlive: 30 * time.Second,       // Keep-alive period
34        }).DialContext,
35    }
36
37    client := &http.Client{
38        Transport: transport,
39        Timeout:   30 * time.Second,           // Total request timeout
40    }
41
42    return client
43}
44
45func main() {
46    client := createOptimizedClient()
47
48    // Make multiple requests to demonstrate connection reuse
49    urls := []string{
50        "https://httpbin.org/get",
51        "https://httpbin.org/headers",
52        "https://httpbin.org/user-agent",
53    }
54
55    fmt.Println("Making requests with optimized HTTP client...")
56    fmt.Println("Connection pooling enabled for better performance\n")
57
58    for i, url := range urls {
59        start := time.Now()
60
61        resp, err := client.Get(url)
62        if err != nil {
63            fmt.Printf("Request %d failed: %v\n", i+1, err)
64            continue
65        }
66
67        // Read and discard body
68        io.Copy(io.Discard, resp.Body)
69        resp.Body.Close()
70
71        duration := time.Since(start)
72        fmt.Printf("Request %d: %s (Status: %d, Duration: %v)\n",
73            i+1, url, resp.StatusCode, duration)
74    }
75
76    fmt.Println("\nConnection pooling configuration:")
77    fmt.Println("  MaxIdleConns:        100")
78    fmt.Println("  MaxIdleConnsPerHost: 10")
79    fmt.Println("  IdleConnTimeout:     90s")
80}

Your First HTTP Server

Let's start with the simplest possible web server. In just 8 lines of code, you'll have a working HTTP server that can handle requests from any web browser or HTTP client.

💡 Key Takeaway: Go's philosophy is "batteries included" - you don't need external frameworks or libraries to build production-ready web services. Everything you need is already in the standard library.

 1// run
 2package main
 3
 4import (
 5    "fmt"
 6    "net/http"
 7)
 8
 9func main() {
10    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
11        fmt.Fprintf(w, "Hello, World!")
12    })
13
14    fmt.Println("Server starting on :8080...")
15    http.ListenAndServe(":8080", nil)
16}

⚠️ Important: The server will run forever until you stop it. This is normal for web services - they're designed to run continuously and handle requests as they come in.

Visit http://localhost:8080 to see your server in action! You'll see "Hello, World!" in your browser.

This article serves as the canonical reference for basic HTTP patterns. Other articles in this tutorial reference this content for:

  • Basic HTTP server setup - Hello World, routing, handlers
  • Request/response handling - JSON, forms, headers, status codes
  • HTTP clients - Making requests, handling responses
  • Middleware patterns - Logging, authentication, CORS
  • REST API development - Complete RESTful patterns
  • Best practices - Error handling, timeouts, security

If you're looking for basic HTTP patterns that are referenced from other articles like cloud-native services, migration guides, or ecosystem tools, you're in the right place!

For domain-specific implementations, see:

Learning Path

  1. Start here - Master basic HTTP patterns in this comprehensive guide
  2. Explore frameworks - See Real-World Applications for framework-specific patterns
  3. Deploy to cloud - Check Cloud Native Go for production deployment
  4. Ensure quality - Review Testing & Quality for API testing strategies

💡 Note: Other articles in this tutorial reference this guide for basic HTTP patterns. If you arrived here from another article looking for fundamental HTTP concepts, you're in the right place!

Practice Exercises

Exercise 1: Basic HTTP Server with Routes

Learning Objectives: Create your first HTTP server, implement basic routing with different endpoints, and handle HTTP requests and responses using Go's net/http package.

Difficulty: ⭐⭐ Beginner
Time Estimate: 20 minutes

Build a simple HTTP server that serves different content at multiple routes. This exercise teaches you the fundamentals of HTTP server creation, request routing, and response writing in Go. You'll learn how to set up HTTP handlers, work with request/response objects, and understand the basic structure of web applications.

Real-World Context: Every web application starts with basic routing. Whether you're building a REST API, a web server, or a microservice, understanding how to create HTTP endpoints and route requests is fundamental. This pattern is used in all web frameworks and is the foundation for building any web-based service or API.

Solution
 1// run
 2package main
 3
 4import (
 5    "fmt"
 6    "net/http"
 7    "time"
 8)
 9
10func homeHandler(w http.ResponseWriter, r *http.Request) {
11    if r.URL.Path != "/" {
12        http.NotFound(w, r)
13        return
14    }
15
16    fmt.Fprintf(w, `
17        <html>
18        <head><title>Home Page</title></head>
19        <body>
20            <h1>Welcome to My Web Server!</h1>
21            <p>This is the home page.</p>
22            <p><a href="/about">About Us</a> | <a href="/api/status">API Status</a></p>
23        </body>
24        </html>
25    `)
26}
27
28func aboutHandler(w http.ResponseWriter, r *http.Request) {
29    fmt.Fprintf(w, `
30        <html>
31        <head><title>About Us</title></head>
32        <body>
33            <h1>About This Server</h1>
34            <p>This is a simple web server built with Go's net/http package.</p>
35            <p><a href="/">Back to Home</a></p>
36        </body>
37        </html>
38    `)
39}
40
41func apiStatusHandler(w http.ResponseWriter, r *http.Request) {
42    w.Header().Set("Content-Type", "application/json")
43    fmt.Fprintf(w, `{
44        "status": "healthy",
45        "timestamp": "%s",
46        "uptime": "running",
47        "version": "1.0.0"
48    }`, time.Now().Format(time.RFC3339))
49}
50
51func main() {
52    // Register handlers for different routes
53    http.HandleFunc("/", homeHandler)
54    http.HandleFunc("/about", aboutHandler)
55    http.HandleFunc("/api/status", apiStatusHandler)
56
57    // Add a custom 404 handler
58    http.NotFoundHandler()
59
60    fmt.Println("Server starting on :8080...")
61    fmt.Println("Available routes:")
62    fmt.Println("  /           - Home page")
63    fmt.Println("  /about      - About page")
64    fmt.Println("  /api/status - API status")
65
66    err := http.ListenAndServe(":8080", nil)
67    if err != nil {
68        fmt.Println("Server error:", err)
69    }
70}

Exercise 2: RESTful API with JSON

Learning Objectives: Build a complete REST API, handle JSON requests and responses, implement CRUD operations, and understand HTTP methods.

Difficulty: ⭐⭐⭐ Intermediate
Time Estimate: 30 minutes

Create a RESTful API for managing a collection of books with full CRUD operations. This exercise teaches you to work with JSON encoding/decoding, handle different HTTP methods, implement RESTful design patterns, and build a complete API that can be consumed by frontend applications.

Real-World Context: REST APIs are the backbone of modern web applications. Every mobile app, single-page application, and microservice architecture relies on RESTful APIs for data communication. Understanding how to build robust, well-designed APIs is essential for any backend developer working on web applications or distributed systems.

Solution
  1// run
  2package main
  3
  4import (
  5    "encoding/json"
  6    "fmt"
  7    "log"
  8    "net/http"
  9    "sync"
 10    "time"
 11)
 12
 13type Book struct {
 14    ID        int       `json:"id"`
 15    Title     string    `json:"title"`
 16    Author    string    `json:"author"`
 17    Published int       `json:"published"`
 18    ISBN      string    `json:"isbn"`
 19    CreatedAt time.Time `json:"created_at"`
 20    UpdatedAt time.Time `json:"updated_at"`
 21}
 22
 23type BookStore struct {
 24    books   map[int]Book
 25    nextID  int
 26    mutex   sync.RWMutex
 27}
 28
 29var store = &BookStore{
 30    books:  make(map[int]Book),
 31    nextID: 1,
 32}
 33
 34func main() {
 35    // Initialize with some sample data
 36    store.books[1] = Book{
 37        ID:        1,
 38        Title:     "The Go Programming Language",
 39        Author:    "Alan Donovan",
 40        Published: 2015,
 41        ISBN:      "978-0134190440",
 42        CreatedAt: time.Now(),
 43        UpdatedAt: time.Now(),
 44    }
 45    store.nextID = 2
 46
 47    // Define routes
 48    http.HandleFunc("/api/books", booksHandler)
 49    http.HandleFunc("/api/books/", bookHandler)
 50
 51    // Enable CORS
 52    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 53        w.Header().Set("Access-Control-Allow-Origin", "*")
 54        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
 55        w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
 56
 57        if r.Method == "OPTIONS" {
 58            w.WriteHeader(http.StatusOK)
 59            return
 60        }
 61
 62        http.NotFound(w, r)
 63    })
 64
 65    fmt.Println("Book API Server starting on :8080...")
 66    fmt.Println("Available endpoints:")
 67    fmt.Println("  GET    /api/books       - List all books")
 68    fmt.Println("  POST   /api/books       - Create new book")
 69    fmt.Println("  GET    /api/books/{id}  - Get specific book")
 70    fmt.Println("  PUT    /api/books/{id}  - Update book")
 71    fmt.Println("  DELETE /api/books/{id}  - Delete book")
 72
 73    log.Fatal(http.ListenAndServe(":8080", nil))
 74}
 75
 76func booksHandler(w http.ResponseWriter, r *http.Request) {
 77    w.Header().Set("Access-Control-Allow-Origin", "*")
 78    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
 79    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
 80
 81    if r.Method == "OPTIONS" {
 82        w.WriteHeader(http.StatusOK)
 83        return
 84    }
 85
 86    w.Header().Set("Content-Type", "application/json")
 87
 88    switch r.Method {
 89    case "GET":
 90        store.mutex.RLock()
 91        defer store.mutex.RUnlock()
 92
 93        books := make([]Book, 0, len(store.books))
 94        for _, book := range store.books {
 95            books = append(books, book)
 96        }
 97
 98        json.NewEncoder(w).Encode(books)
 99
100    case "POST":
101        var book Book
102        if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
103            http.Error(w, err.Error(), http.StatusBadRequest)
104            return
105        }
106
107        store.mutex.Lock()
108        defer store.mutex.Unlock()
109
110        book.ID = store.nextID
111        book.CreatedAt = time.Now()
112        book.UpdatedAt = time.Now()
113        store.books[book.ID] = book
114        store.nextID++
115
116        w.WriteHeader(http.StatusCreated)
117        json.NewEncoder(w).Encode(book)
118
119    default:
120        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
121    }
122}
123
124func bookHandler(w http.ResponseWriter, r *http.Request) {
125    w.Header().Set("Access-Control-Allow-Origin", "*")
126    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
127    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
128
129    if r.Method == "OPTIONS" {
130        w.WriteHeader(http.StatusOK)
131        return
132    }
133
134    w.Header().Set("Content-Type", "application/json")
135
136    // Extract ID from URL path
137    path := r.URL.Path
138    if len(path) < len("/api/books/") {
139        http.NotFound(w, r)
140        return
141    }
142
143    idStr := path[len("/api/books/"):]
144    var id int
145    if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
146        http.Error(w, "Invalid book ID", http.StatusBadRequest)
147        return
148    }
149
150    store.mutex.RLock()
151    book, exists := store.books[id]
152    store.mutex.RUnlock()
153
154    if !exists {
155        http.Error(w, "Book not found", http.StatusNotFound)
156        return
157    }
158
159    switch r.Method {
160    case "GET":
161        json.NewEncoder(w).Encode(book)
162
163    case "PUT":
164        var updatedBook Book
165        if err := json.NewDecoder(r.Body).Decode(&updatedBook); err != nil {
166            http.Error(w, err.Error(), http.StatusBadRequest)
167            return
168        }
169
170        store.mutex.Lock()
171        defer store.mutex.Unlock()
172
173        // Preserve immutable fields
174        updatedBook.ID = book.ID
175        updatedBook.CreatedAt = book.CreatedAt
176        updatedBook.UpdatedAt = time.Now()
177
178        store.books[id] = updatedBook
179        json.NewEncoder(w).Encode(updatedBook)
180
181    case "DELETE":
182        store.mutex.Lock()
183        defer store.mutex.Unlock()
184
185        delete(store.books, id)
186        w.WriteHeader(http.StatusNoContent)
187
188    default:
189        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
190    }
191}

Exercise 3: HTTP Middleware Chain

Learning Objectives: Implement HTTP middleware, create reusable request processing components, understand request chaining, and build modular web applications.

Difficulty: ⭐⭐⭐ Intermediate
Time Estimate: 25 minutes

Build a middleware system that adds logging, authentication, and request timing to your HTTP server. This exercise teaches you to create composable middleware functions, implement request context passing, handle authentication headers, and understand how modern web frameworks process requests through middleware chains.

Real-World Context: Middleware is essential in production web applications for cross-cutting concerns like authentication, logging, rate limiting, and error handling. Every professional web framework uses middleware patterns to keep code modular and reusable. Understanding middleware is crucial for building secure, observable, and maintainable web applications.

Solution
  1// run
  2package main
  3
  4import (
  5    "context"
  6    "fmt"
  7    "log"
  8    "net/http"
  9    "time"
 10)
 11
 12// Middleware type represents an HTTP middleware function
 13type Middleware func(http.Handler) http.Handler
 14
 15// LoggerMiddleware logs each request
 16func LoggerMiddleware(next http.Handler) http.Handler {
 17    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 18        start := time.Now()
 19
 20        // Create a response writer wrapper to capture status code
 21        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
 22
 23        // Call the next handler
 24        next.ServeHTTP(wrapped, r)
 25
 26        // Log the request
 27        duration := time.Since(start)
 28        log.Printf("[%s] %s %s %d %v",
 29            r.Method,
 30            r.URL.Path,
 31            r.RemoteAddr,
 32            wrapped.statusCode,
 33            duration,
 34        )
 35    })
 36}
 37
 38// AuthMiddleware adds simple authentication
 39func AuthMiddleware(next http.Handler) http.Handler {
 40    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 41        // Skip auth for health check
 42        if r.URL.Path == "/health" {
 43            next.ServeHTTP(w, r)
 44            return
 45        }
 46
 47        token := r.Header.Get("Authorization")
 48        if token != "Bearer secret-token" {
 49            http.Error(w, "Unauthorized", http.StatusUnauthorized)
 50            return
 51        }
 52
 53        // Add user info to context
 54        ctx := context.WithValue(r.Context(), "user", "authenticated-user")
 55        next.ServeHTTP(w, r.WithContext(ctx))
 56    })
 57}
 58
 59// CORSMiddleware handles CORS headers
 60func CORSMiddleware(next http.Handler) http.Handler {
 61    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 62        w.Header().Set("Access-Control-Allow-Origin", "*")
 63        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
 64        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
 65
 66        if r.Method == "OPTIONS" {
 67            w.WriteHeader(http.StatusOK)
 68            return
 69        }
 70
 71        next.ServeHTTP(w, r)
 72    })
 73}
 74
 75// RateLimitMiddleware adds simple rate limiting
 76func RateLimitMiddleware(next http.Handler) http.Handler {
 77    // Simple in-memory rate limiter
 78    requests := make(map[string][]time.Time)
 79
 80    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 81        clientIP := r.RemoteAddr
 82        now := time.Now()
 83
 84        // Clean old requests
 85        if times, exists := requests[clientIP]; exists {
 86            var validTimes []time.Time
 87            for _, t := range times {
 88                if now.Sub(t) < time.Minute {
 89                    validTimes = append(validTimes, t)
 90                }
 91            }
 92            requests[clientIP] = validTimes
 93        }
 94
 95        // Check rate limit
 96        if len(requests[clientIP]) >= 10 {
 97            http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
 98            return
 99        }
100
101        // Add current request
102        requests[clientIP] = append(requests[clientIP], now)
103        next.ServeHTTP(w, r)
104    })
105}
106
107// responseWriter wrapper to capture status code
108type responseWriter struct {
109    http.ResponseWriter
110    statusCode int
111}
112
113func (rw *responseWriter) WriteHeader(code int) {
114    rw.statusCode = code
115    rw.ResponseWriter.WriteHeader(code)
116}
117
118// ApplyMiddleware chains multiple middleware functions
119func ApplyMiddleware(handler http.Handler, middleware ...Middleware) http.Handler {
120    for _, mw := range middleware {
121        handler = mw(handler)
122    }
123    return handler
124}
125
126// Route handlers
127func homeHandler(w http.ResponseWriter, r *http.Request) {
128    user := r.Context().Value("user")
129    if user != nil {
130        fmt.Fprintf(w, "Hello, %v! Welcome to the protected home page.\n", user)
131    } else {
132        fmt.Fprintf(w, "Welcome to the home page!\n")
133    }
134}
135
136func apiHandler(w http.ResponseWriter, r *http.Request) {
137    user := r.Context().Value("user")
138    if user != nil {
139        fmt.Fprintf(w, `{"message": "API endpoint accessed by %v", "timestamp": "%s"}`, user, time.Now().Format(time.RFC3339))
140    } else {
141        fmt.Fprintf(w, `{"message": "API endpoint", "timestamp": "%s"}`, time.Now().Format(time.RFC3339))
142    }
143}
144
145func healthHandler(w http.ResponseWriter, r *http.Request) {
146    fmt.Fprintf(w, `{"status": "healthy", "timestamp": "%s"}`, time.Now().Format(time.RFC3339))
147}
148
149func main() {
150    // Create routes
151    mux := http.NewServeMux()
152    mux.HandleFunc("/", homeHandler)
153    mux.HandleFunc("/api", apiHandler)
154    mux.HandleFunc("/health", healthHandler)
155
156    // Apply middleware chain
157    // Note: Order matters! Logger wraps everything, Auth is applied before rate limiting
158    handler := ApplyMiddleware(mux,
159        LoggerMiddleware,    // First: Log all requests
160        CORSMiddleware,      // Second: Handle CORS
161        RateLimitMiddleware, // Third: Apply rate limiting
162        AuthMiddleware,      // Fourth: Check authentication
163    )
164
165    fmt.Println("Middleware Demo Server starting on :8080...")
166    fmt.Println("Available endpoints:")
167    fmt.Println("  GET /          - Home page")
168    fmt.Println("  GET /api       - API endpoint")
169    fmt.Println("  GET /health    - Health check")
170    fmt.Println("\nTo test protected endpoints, include header:")
171    fmt.Println("  Authorization: Bearer secret-token")
172    fmt.Println("\nRate limit: 10 requests per minute per IP")
173
174    log.Fatal(http.ListenAndServe(":8080", handler))
175}

Exercise 4: File Server with Static Assets

Learning Objectives: Create a file server for static content, handle MIME types, implement directory listing, and understand static file serving best practices.

Difficulty: ⭐⭐ Beginner
Time Estimate: 20 minutes

Build a complete file server that can serve static files from a directory, with support for directory browsing and proper MIME type handling. This exercise teaches you to work with the file system through HTTP, handle static asset serving, implement security controls, and understand how web servers serve static content.

Real-World Context: Every web application needs to serve static files - CSS stylesheets, JavaScript files, images, fonts, and other assets. Understanding how to build an efficient file server is fundamental for web development. This pattern is used in development servers, content delivery systems, and as the foundation for serving web applications.

Solution
  1// run
  2package main
  3
  4import (
  5    "fmt"
  6    "io"
  7    "log"
  8    "net/http"
  9    "os"
 10    "path/filepath"
 11    "strings"
 12    "time"
 13)
 14
 15func main() {
 16    // Create a directory for static files
 17    staticDir := "./static"
 18    if err := os.MkdirAll(staticDir, 0755); err != nil {
 19        log.Fatal("Failed to create static directory:", err)
 20    }
 21
 22    // Create some sample files
 23    createSampleFiles(staticDir)
 24
 25    // Custom file server with additional features
 26    fs := &customFileServer{
 27        root:     staticDir,
 28        dirCache: make(map[string]*dirInfo),
 29    }
 30
 31    // Register handlers
 32    http.HandleFunc("/", fs.handleRequest)
 33    http.HandleFunc("/api/fileinfo", fs.handleFileInfo)
 34
 35    fmt.Println("Static File Server starting on :8080...")
 36    fmt.Println("Serving files from:", staticDir)
 37    fmt.Println("Available endpoints:")
 38    fmt.Println("  GET /                 - Browse files")
 39    fmt.Println("  GET /filename.html    - Serve specific file")
 40    fmt.Println("  GET /api/fileinfo?path=filename - Get file info")
 41
 42    log.Fatal(http.ListenAndServe(":8080", nil))
 43}
 44
 45type customFileServer struct {
 46    root     string
 47    dirCache map[string]*dirInfo
 48}
 49
 50type dirInfo struct {
 51    entries []os.DirEntry
 52    modTime time.Time
 53}
 54
 55func (fs *customFileServer) handleRequest(w http.ResponseWriter, r *http.Request) {
 56    // Clean the URL path
 57    path := filepath.Clean(r.URL.Path)
 58    if path == "/" {
 59        path = "/index.html"
 60    }
 61
 62    // Security check: prevent directory traversal
 63    if strings.Contains(path, "..") {
 64        http.Error(w, "Forbidden", http.StatusForbidden)
 65        return
 66    }
 67
 68    fullPath := filepath.Join(fs.root, strings.TrimPrefix(path, "/"))
 69
 70    // Check if path exists
 71    info, err := os.Stat(fullPath)
 72    if err != nil {
 73        if os.IsNotExist(err) {
 74            http.NotFound(w, r)
 75        } else {
 76            http.Error(w, err.Error(), http.StatusInternalServerError)
 77        }
 78        return
 79    }
 80
 81    if info.IsDir() {
 82        fs.serveDirectory(w, r, fullPath, path)
 83    } else {
 84        fs.serveFile(w, r, fullPath, info)
 85    }
 86}
 87
 88func (fs *customFileServer) serveFile(w http.ResponseWriter, r *http.Request, fullPath string, info os.FileInfo) {
 89    // Set appropriate MIME type
 90    contentType := getContentType(fullPath)
 91    w.Header().Set("Content-Type", contentType)
 92
 93    // Set cache headers
 94    w.Header().Set("Cache-Control", "public, max-age=3600")
 95    w.Header().Set("Last-Modified", info.ModTime().Format(http.TimeFormat))
 96
 97    // Check if client has cached version
 98    if ifModifiedSince := r.Header.Get("If-Modified-Since"); ifModifiedSince != "" {
 99        if t, err := http.ParseTime(ifModifiedSince); err == nil && t.After(info.ModTime()) {
100            w.WriteHeader(http.StatusNotModified)
101            return
102        }
103    }
104
105    // Serve the file
106    http.ServeFile(w, r, fullPath)
107}
108
109func (fs *customFileServer) serveDirectory(w http.ResponseWriter, r *http.Request, fullPath, urlPath string) {
110    // Look for index.html
111    indexPath := filepath.Join(fullPath, "index.html")
112    if info, err := os.Stat(indexPath); err == nil && !info.IsDir() {
113        fs.serveFile(w, r, indexPath, info)
114        return
115    }
116
117    // Serve directory listing
118    entries, err := os.ReadDir(fullPath)
119    if err != nil {
120        http.Error(w, err.Error(), http.StatusInternalServerError)
121        return
122    }
123
124    w.Header().Set("Content-Type", "text/html; charset=utf-8")
125    fmt.Fprintf(w, `<!DOCTYPE html>
126<html>
127<head>
128    <title>Directory listing for %s</title>
129    <style>
130        body { font-family: Arial, sans-serif; margin: 20px; }
131        table { border-collapse: collapse; width: 100%%; }
132        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
133        th { background-color: #f2f2f2; }
134        .size { text-align: right; }
135        .dir { font-weight: bold; color: #0066cc; }
136    </style>
137</head>
138<body>
139    <h1>Directory listing for %s</h1>
140    <table>
141        <tr>
142            <th>Name</th>
143            <th>Size</th>
144            <th>Modified</th>
145        </tr>`, urlPath, urlPath)
146
147    // Parent directory link
148    if urlPath != "/" {
149        parentPath := filepath.Dir(urlPath)
150        if parentPath == "." {
151            parentPath = "/"
152        }
153        fmt.Fprintf(w, `
154        <tr>
155            <td><a href="%s">..</a></td>
156            <td class="size">-</td>
157            <td>-</td>
158        </tr>`, parentPath)
159    }
160
161    // Directory entries
162    for _, entry := range entries {
163        info, _ := entry.Info()
164        name := entry.Name()
165        href := filepath.Join(urlPath, name)
166
167        class := ""
168        if entry.IsDir() {
169            name += "/"
170            class = "class=\"dir\""
171        }
172
173        size := "-"
174        if !entry.IsDir() {
175            size = formatBytes(info.Size())
176        }
177
178        modified := info.ModTime().Format("2006-01-02 15:04:05")
179
180        fmt.Fprintf(w, `
181        <tr>
182            <td><a href="%s" %s>%s</a></td>
183            <td class="size">%s</td>
184            <td>%s</td>
185        </tr>`, href, class, name, size, modified)
186    }
187
188    fmt.Fprintf(w, `
189    </table>
190    <p><em>Served by Go Static File Server</em></p>
191</body>
192</html>`)
193}
194
195func (fs *customFileServer) handleFileInfo(w http.ResponseWriter, r *http.Request) {
196    path := r.URL.Query().Get("path")
197    if path == "" {
198        http.Error(w, "path parameter required", http.StatusBadRequest)
199        return
200    }
201
202    // Security check
203    if strings.Contains(path, "..") {
204        http.Error(w, "Forbidden", http.StatusForbidden)
205        return
206    }
207
208    fullPath := filepath.Join(fs.root, path)
209    info, err := os.Stat(fullPath)
210    if err != nil {
211        http.Error(w, "File not found", http.StatusNotFound)
212        return
213    }
214
215    w.Header().Set("Content-Type", "application/json")
216
217    if info.IsDir() {
218        entries, _ := os.ReadDir(fullPath)
219        fmt.Fprintf(w, `{
220            "name": "%s",
221            "type": "directory",
222            "size": 0,
223            "modified": "%s",
224            "entries": %d
225        }`, info.Name(), info.ModTime().Format(time.RFC3339), len(entries))
226    } else {
227        fmt.Fprintf(w, `{
228            "name": "%s",
229            "type": "file",
230            "size": %d,
231            "modified": "%s",
232            "content_type": "%s"
233        }`, info.Name(), info.Size(), info.ModTime().Format(time.RFC3339), getContentType(fullPath))
234    }
235}
236
237func getContentType(path string) string {
238    ext := strings.ToLower(filepath.Ext(path))
239    switch ext {
240    case ".html":
241        return "text/html; charset=utf-8"
242    case ".css":
243        return "text/css; charset=utf-8"
244    case ".js":
245        return "application/javascript"
246    case ".json":
247        return "application/json"
248    case ".png":
249        return "image/png"
250    case ".jpg", ".jpeg":
251        return "image/jpeg"
252    case ".gif":
253        return "image/gif"
254    case ".svg":
255        return "image/svg+xml"
256    case ".txt":
257        return "text/plain; charset=utf-8"
258    case ".pdf":
259        return "application/pdf"
260    default:
261        return "application/octet-stream"
262    }
263}
264
265func formatBytes(bytes int64) string {
266    const unit = 1024
267    if bytes < unit {
268        return fmt.Sprintf("%d B", bytes)
269    }
270    div, exp := int64(unit), 0
271    for n := bytes / unit; n >= unit; n /= unit {
272        div *= unit
273        exp++
274    }
275    return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
276}
277
278func createSampleFiles(dir string) {
279    files := map[string]string{
280        "index.html": `<!DOCTYPE html>
281<html>
282<head>
283    <title>Go Static File Server</title>
284    <link rel="stylesheet" href="/style.css">
285</head>
286<body>
287    <h1>Welcome to Go Static File Server!</h1>
288    <p>This is a sample HTML file served by our Go server.</p>
289    <img src="/logo.png" alt="Logo" width="100">
290    <script src="/app.js"></script>
291</body>
292</html>`,
293        "style.css": `body {
294    font-family: Arial, sans-serif;
295    max-width: 800px;
296    margin: 0 auto;
297    padding: 20px;
298    background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%);
299    color: white;
300    min-height: 100vh;
301}
302
303h1 {
304    text-align: center;
305    text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
306}
307
308img {
309    display: block;
310    margin: 20px auto;
311    border-radius: 10px;
312    box-shadow: 0 4px 8px rgba(0,0,0,0.3);
313}`,
314        "app.js": `console.log('Hello from Go Static File Server!');
315document.addEventListener('DOMContentLoaded', function() {
316    const h1 = document.querySelector('h1');
317    h1.addEventListener('click', function() {
318        this.style.color = '#' + Math.floor(Math.random()*16777215).toString(16);
319    });
320
321    fetch('/api/fileinfo?path=index.html')
322        .then(response => response.json())
323        .then(data => console.log('File info:', data));
324});`,
325        "README.txt": `This is a sample text file.
326It contains multiple lines.
327You can view this file through the file server.
328Directory listing is also supported!`,
329    }
330
331    for filename, content := range files {
332        filepath := filepath.Join(dir, filename)
333        if err := os.WriteFile(filepath, []byte(content), 0644); err != nil {
334            log.Printf("Failed to create %s: %v", filename, err)
335        }
336    }
337
338    // Create a subdirectory
339    subdir := filepath.Join(dir, "docs")
340    if err := os.MkdirAll(subdir, 0755); err == nil {
341        os.WriteFile(filepath.Join(subdir, "about.txt"), []byte("This is in a subdirectory."), 0644)
342    }
343}

Exercise 5: HTTP Client with Retry Logic

Learning Objectives: Build HTTP clients, implement retry mechanisms, handle network errors, work with timeouts, and understand resilient API communication patterns.

Difficulty: ⭐⭐⭐ Intermediate
Time Estimate: 25 minutes

Create an HTTP client that communicates with external APIs with built-in retry logic, exponential backoff, timeout handling, and error recovery. This exercise teaches you to build robust client applications, handle network failures, implement retry strategies, and understand how production-grade HTTP clients work.

Real-World Context: Every application that consumes external APIs needs robust error handling and retry logic. Network connections fail, APIs return temporary errors, and services go down. Understanding how to build resilient HTTP clients is crucial for building reliable distributed systems that can handle partial failures gracefully.

Solution
  1// run
  2package main
  3
  4import (
  5    "bytes"
  6    "context"
  7    "encoding/json"
  8    "fmt"
  9    "io"
 10    "log"
 11    "math"
 12    "math/rand"
 13    "net/http"
 14    "net/url"
 15    "time"
 16)
 17
 18type HTTPClient struct {
 19    client    *http.Client
 20    maxRetries int
 21    backoff   BackoffStrategy
 22}
 23
 24type BackoffStrategy interface {
 25    Delay(attempt int) time.Duration
 26}
 27
 28type ExponentialBackoff struct {
 29    BaseDelay time.Duration
 30    MaxDelay  time.Duration
 31    Factor    float64
 32    Jitter    bool
 33}
 34
 35func (eb ExponentialBackoff) Delay(attempt int) time.Duration {
 36    delay := float64(eb.BaseDelay) * math.Pow(eb.Factor, float64(attempt))
 37    if delay > float64(eb.MaxDelay) {
 38        delay = float64(eb.MaxDelay)
 39    }
 40
 41    if eb.Jitter {
 42        // Add random jitter to prevent thundering herd
 43        jitter := rand.Float64() * 0.1 * delay
 44        delay += jitter
 45    }
 46
 47    return time.Duration(delay)
 48}
 49
 50type APIResponse struct {
 51    Message string `json:"message"`
 52    Data    interface{} `json:"data,omitempty"`
 53    Error   string `json:"error,omitempty"`
 54}
 55
 56func NewHTTPClient(timeout time.Duration, maxRetries int) *HTTPClient {
 57    return &HTTPClient{
 58        client: &http.Client{
 59            Timeout: timeout,
 60            Transport: &retryTransport{
 61                underlying: http.DefaultTransport,
 62            },
 63        },
 64        maxRetries: maxRetries,
 65        backoff: ExponentialBackoff{
 66            BaseDelay: 100 * time.Millisecond,
 67            MaxDelay:  5 * time.Second,
 68            Factor:    2.0,
 69            Jitter:    true,
 70        },
 71    }
 72}
 73
 74func (c *HTTPClient) DoWithRetry(req *http.Request) (*http.Response, error) {
 75    var lastErr error
 76
 77    for attempt := 0; attempt <= c.maxRetries; attempt++ {
 78        if attempt > 0 {
 79            // Wait before retry
 80            delay := c.backoff.Delay(attempt - 1)
 81            log.Printf("Attempt %d failed, retrying in %v...", attempt, delay)
 82            time.Sleep(delay)
 83
 84            // Create a new request for retry
 85            if req.Body != nil {
 86                // This is a simplified example - in practice, you'd need to
 87                // store and recreate the request body
 88                bodyBytes, _ := io.ReadAll(req.Body)
 89                req.Body.Close()
 90                req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
 91            }
 92        }
 93
 94        resp, err := c.client.Do(req)
 95        if err == nil {
 96            // Check if response indicates a temporary failure
 97            if resp.StatusCode >= 500 || resp.StatusCode == 429 {
 98                lastErr = fmt.Errorf("server returned status %d", resp.StatusCode)
 99                resp.Body.Close()
100                continue
101            }
102            return resp, nil
103        }
104
105        lastErr = err
106
107        // Don't retry on certain errors
108        if !isRetryableError(err) {
109            break
110        }
111    }
112
113    return nil, fmt.Errorf("after %d attempts, last error: %v", c.maxRetries+1, lastErr)
114}
115
116func isRetryableError(err error) bool {
117    // Check for retryable errors
118    if urlErr, ok := err.(*url.Error); ok {
119        if urlErr.Timeout() {
120            return true
121        }
122        if urlErr.Temporary() {
123            return true
124        }
125    }
126
127    // Add more specific error checks as needed
128    return true
129}
130
131func (c *HTTPClient) GetJSON(url string, target interface{}) error {
132    req, err := http.NewRequest("GET", url, nil)
133    if err != nil {
134        return err
135    }
136
137    req.Header.Set("Accept", "application/json")
138    req.Header.Set("User-Agent", "GoHTTPClient/1.0")
139
140    resp, err := c.DoWithRetry(req)
141    if err != nil {
142        return err
143    }
144    defer resp.Body.Close()
145
146    if resp.StatusCode != http.StatusOK {
147        return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
148    }
149
150    return json.NewDecoder(resp.Body).Decode(target)
151}
152
153func (c *HTTPClient) PostJSON(url string, data interface{}, target interface{}) error {
154    bodyBytes, err := json.Marshal(data)
155    if err != nil {
156        return err
157    }
158
159    req, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes))
160    if err != nil {
161        return err
162    }
163
164    req.Header.Set("Content-Type", "application/json")
165    req.Header.Set("Accept", "application/json")
166    req.Header.Set("User-Agent", "GoHTTPClient/1.0")
167
168    resp, err := c.DoWithRetry(req)
169    if err != nil {
170        return err
171    }
172    defer resp.Body.Close()
173
174    if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
175        return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
176    }
177
178    if target != nil {
179        return json.NewDecoder(resp.Body).Decode(target)
180    }
181
182    return nil
183}
184
185// retryTransport implements custom transport logic
186type retryTransport struct {
187    underlying http.RoundTripper
188}
189
190func (t *retryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
191    // Add custom headers or modify request here
192    req.Header.Set("X-Request-ID", generateRequestID())
193
194    return t.underlying.RoundTrip(req)
195}
196
197func generateRequestID() string {
198    return fmt.Sprintf("%d", rand.Int63())
199}
200
201func main() {
202    client := NewHTTPClient(10*time.Second, 3)
203
204    fmt.Println("HTTP Client with Retry Logic Demo")
205    fmt.Println("================================")
206
207    // Test 1: Successful request
208    fmt.Println("\n1. Testing successful request to httpbin.org:")
209    var resp1 APIResponse
210    err := client.GetJSON("https://httpbin.org/get", &resp1)
211    if err != nil {
212        log.Printf("Error: %v", err)
213    } else {
214        fmt.Printf("Success! Message: %s\n", resp1.Message)
215    }
216
217    // Test 2: POST request
218    fmt.Println("\n2. Testing POST request:")
219    postData := map[string]interface{}{
220        "name":  "Go Client",
221        "value": 42,
222    }
223
224    var resp2 APIResponse
225    err = client.PostJSON("https://httpbin.org/post", postData, &resp2)
226    if err != nil {
227        log.Printf("Error: %v", err)
228    } else {
229        fmt.Printf("Success! Posted data and received response\n")
230    }
231
232    // Test 3: Test retry logic with a failing endpoint
233    fmt.Println("\n3. Testing retry logic with invalid domain:")
234    var resp3 APIResponse
235    err = client.GetJSON("https://invalid-domain-that-does-not-exist.com/api", &resp3)
236    if err != nil {
237        fmt.Printf("Expected error after retries: %v\n", err)
238    }
239
240    // Test 4: Test timeout handling
241    fmt.Println("\n4. Testing timeout handling with slow endpoint:")
242    slowClient := NewHTTPClient(2*time.Second, 2) // 2 second timeout
243    var resp4 APIResponse
244    err = slowClient.GetJSON("https://httpbin.org/delay/5", &resp4) // 5 second delay
245    if err != nil {
246        fmt.Printf("Expected timeout error: %v\n", err)
247    }
248
249    fmt.Println("\nDemo completed!")
250    fmt.Println("\nKey concepts demonstrated:")
251    fmt.Println("- Exponential backoff with jitter")
252    fmt.Println("- Retry logic for temporary failures")
253    fmt.Println("- HTTP timeout handling")
254    fmt.Println("- JSON request/response handling")
255    fmt.Println("- Custom transport and headers")
256}

Exercise 6: WebSocket Chat Server

Learning Objectives: Implement WebSocket communication, handle concurrent connections, manage real-time data broadcasting, and build interactive web applications.

Difficulty: ⭐⭐⭐⭐ Advanced
Time Estimate: 40 minutes

Build a real-time chat server using WebSockets that supports multiple concurrent users, message broadcasting, and connection management. This exercise teaches you to work with WebSocket protocols, manage concurrent connections, implement real-time communication patterns, and understand how modern chat and notification systems work.

Real-World Context: WebSockets power real-time features in modern web applications - chat systems, live notifications, collaborative editing tools, real-time dashboards, and multiplayer games. Understanding WebSocket programming is essential for building interactive, real-time web applications that require instant communication between clients and servers.

Solution
  1// run
  2package main
  3
  4import (
  5    "encoding/json"
  6    "fmt"
  7    "log"
  8    "net/http"
  9    "sync"
 10    "time"
 11
 12    "github.com/gorilla/websocket"
 13)
 14
 15// Message types
 16const (
 17    MessageTypeJoin     = "join"
 18    MessageTypeLeave    = "leave"
 19    MessageTypeMessage  = "message"
 20    MessageTypeUsers    = "users"
 21)
 22
 23// Message represents a chat message
 24type Message struct {
 25    Type      string    `json:"type"`
 26    Username  string    `json:"username,omitempty"`
 27    Content   string    `json:"content,omitempty"`
 28    Timestamp time.Time `json:"timestamp"`
 29    UserCount int       `json:"userCount,omitempty"`
 30}
 31
 32// Client represents a WebSocket client
 33type Client struct {
 34    hub     *Hub
 35    conn    *websocket.Conn
 36    send    chan Message
 37    username string
 38}
 39
 40// Hub manages the set of active clients and broadcasts messages
 41type Hub struct {
 42    clients    map[*Client]bool
 43    broadcast  chan Message
 44    register   chan *Client
 45    unregister chan *Client
 46    mutex      sync.RWMutex
 47}
 48
 49var upgrader = websocket.Upgrader{
 50    CheckOrigin: func(r *http.Request) bool {
 51        // Allow connections from any origin in development
 52        return true
 53    },
 54}
 55
 56func newHub() *Hub {
 57    return &Hub{
 58        broadcast:  make(chan Message),
 59        register:   make(chan *Client),
 60        unregister: make(chan *Client),
 61        clients:    make(map[*Client]bool),
 62    }
 63}
 64
 65func (h *Hub) run() {
 66    for {
 67        select {
 68        case client := <-h.register:
 69            h.mutex.Lock()
 70            h.clients[client] = true
 71            h.mutex.Unlock()
 72
 73            // Broadcast user join message
 74            userCount := len(h.clients)
 75            joinMsg := Message{
 76                Type:      MessageTypeJoin,
 77                Username:  client.username,
 78                Content:   fmt.Sprintf("%s joined the chat", client.username),
 79                Timestamp: time.Now(),
 80                UserCount: userCount,
 81            }
 82            h.broadcast <- joinMsg
 83
 84            // Send updated user list to all clients
 85            h.broadcastUserList()
 86
 87        case client := <-h.unregister:
 88            h.mutex.Lock()
 89            if _, ok := h.clients[client]; ok {
 90                delete(h.clients, client)
 91                close(client.send)
 92            }
 93            h.mutex.Unlock()
 94
 95            // Broadcast user leave message
 96            userCount := len(h.clients)
 97            leaveMsg := Message{
 98                Type:      MessageTypeLeave,
 99                Username:  client.username,
100                Content:   fmt.Sprintf("%s left the chat", client.username),
101                Timestamp: time.Now(),
102                UserCount: userCount,
103            }
104            h.broadcast <- leaveMsg
105
106            // Send updated user list to all clients
107            h.broadcastUserList()
108
109        case message := <-h.broadcast:
110            h.mutex.RLock()
111            for client := range h.clients {
112                select {
113                case client.send <- message:
114                default:
115                    close(client.send)
116                    delete(h.clients, client)
117                }
118            }
119            h.mutex.RUnlock()
120        }
121    }
122}
123
124func (h *Hub) broadcastUserList() {
125    h.mutex.RLock()
126    defer h.mutex.RUnlock()
127
128    var usernames []string
129    for client := range h.clients {
130        usernames = append(usernames, client.username)
131    }
132
133    userMsg := Message{
134        Type:      MessageTypeUsers,
135        Content:   fmt.Sprintf("%v", usernames),
136        UserCount: len(h.clients),
137        Timestamp: time.Now(),
138    }
139
140    h.broadcast <- userMsg
141}
142
143func (c *Client) readPump() {
144    defer func() {
145        c.hub.unregister <- c
146        c.conn.Close()
147    }()
148
149    c.conn.SetReadLimit(512)
150    c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
151    c.conn.SetPongHandler(func(string) error {
152        c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
153        return nil
154    })
155
156    for {
157        var msg Message
158        err := c.conn.ReadJSON(&msg)
159        if err != nil {
160            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
161                log.Printf("error: %v", err)
162            }
163            break
164        }
165
166        // Set message metadata
167        msg.Type = MessageTypeMessage
168        msg.Username = c.username
169        msg.Timestamp = time.Now()
170
171        // Broadcast the message
172        c.hub.broadcast <- msg
173    }
174}
175
176func (c *Client) writePump() {
177    ticker := time.NewTicker(54 * time.Second)
178    defer func() {
179        ticker.Stop()
180        c.conn.Close()
181    }()
182
183    for {
184        select {
185        case message, ok := <-c.send:
186            c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
187            if !ok {
188                c.conn.WriteMessage(websocket.CloseMessage, []byte{})
189                return
190            }
191
192            if err := c.conn.WriteJSON(message); err != nil {
193                return
194            }
195
196        case <-ticker.C:
197            c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
198            if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
199                return
200            }
201        }
202    }
203}
204
205func serveWebSocket(hub *Hub, w http.ResponseWriter, r *http.Request) {
206    conn, err := upgrader.Upgrade(w, r, nil)
207    if err != nil {
208        log.Println(err)
209        return
210    }
211
212    // Get username from query parameter or session
213    username := r.URL.Query().Get("username")
214    if username == "" {
215        username = fmt.Sprintf("User%d", time.Now().Unix()%1000)
216    }
217
218    client := &Client{
219        hub:      hub,
220        conn:     conn,
221        send:     make(chan Message, 256),
222        username: username,
223    }
224
225    client.hub.register <- client
226
227    // Allow collection of memory referenced by the caller by doing all work in new goroutines
228    go client.writePump()
229    go client.readPump()
230}
231
232func serveHome(w http.ResponseWriter, r *http.Request) {
233    if r.URL.Path != "/" {
234        http.Error(w, "Not found", http.StatusNotFound)
235        return
236    }
237
238    if r.Method != "GET" {
239        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
240        return
241    }
242
243    html := `<!DOCTYPE html>
244<html>
245<head>
246    <title>Go WebSocket Chat</title>
247    <style>
248        body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f0f0f0; }
249        .container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
250        .chat-container { height: 400px; border: 1px solid #ddd; overflow-y: auto; padding: 10px; margin-bottom: 10px; background: #fafafa; }
251        .message { margin-bottom: 10px; padding: 8px; border-radius: 5px; }
252        .message.join { background: #d4edda; color: #155724; }
253        .message.leave { background: #f8d7da; color: #721c24; }
254        .message.users { background: #d1ecf1; color: #0c5460; }
255        .message.chat { background: #e2e3e5; }
256        .username { font-weight: bold; margin-right: 5px; }
257        .timestamp { font-size: 0.8em; color: #666; }
258        .input-container { display: flex; gap: 10px; }
259        #messageInput { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
260        #sendButton { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; }
261        #sendButton:hover { background: #0056b3; }
262        .user-info { margin-bottom: 10px; padding: 10px; background: #e9ecef; border-radius: 5px; }
263        .status { font-size: 0.9em; color: #666; }
264    </style>
265</head>
266<body>
267    <div class="container">
268        <h1>Go WebSocket Chat</h1>
269        <div class="user-info">
270            <div>Username: <strong id="username"></strong></div>
271            <div class="status">Connected users: <strong id="userCount">0</strong></div>
272        </div>
273        <div id="chatContainer" class="chat-container"></div>
274        <div class="input-container">
275            <input type="text" id="messageInput" placeholder="Type a message..." />
276            <button id="sendButton">Send</button>
277        </div>
278    </div>
279
280    <script>
281        const username = prompt("Enter your username:") || "User" + Math.floor(Math.random() * 1000);
282        document.getElementById('username').textContent = username;
283
284        const ws = new WebSocket('ws://localhost:8080/ws?username=' + encodeURIComponent(username));
285        const chatContainer = document.getElementById('chatContainer');
286        const messageInput = document.getElementById('messageInput');
287        const sendButton = document.getElementById('sendButton');
288        const userCountElement = document.getElementById('userCount');
289
290        function addMessage(message) {
291            const messageDiv = document.createElement('div');
292            messageDiv.className = 'message ' + message.type;
293
294            let content = '';
295            if (message.type === 'message') {
296                content = '<span class="username">' + message.username + ':</span> ' + message.content;
297            } else {
298                content = message.content;
299            }
300
301            content += '<span class="timestamp"> ' + new Date(message.timestamp).toLocaleTimeString() + '</span>';
302            messageDiv.innerHTML = content;
303
304            chatContainer.appendChild(messageDiv);
305            chatContainer.scrollTop = chatContainer.scrollHeight;
306        }
307
308        function updateUserCount(count) {
309            userCountElement.textContent = count;
310        }
311
312        ws.onmessage = function(event) {
313            const message = JSON.parse(event.data);
314            addMessage(message);
315            if (message.userCount !== undefined) {
316                updateUserCount(message.userCount);
317            }
318        };
319
320        function sendMessage() {
321            const message = messageInput.value.trim();
322            if (message) {
323                ws.send(JSON.stringify({
324                    type: 'message',
325                    content: message
326                }));
327                messageInput.value = '';
328            }
329        }
330
331        sendButton.onclick = sendMessage;
332        messageInput.onkeypress = function(e) {
333            if (e.key === 'Enter') {
334                sendMessage();
335            }
336        };
337
338        ws.onclose = function() {
339            addMessage({
340                type: 'leave',
341                content: 'Connection closed',
342                timestamp: new Date().toISOString()
343            });
344        };
345
346        ws.onerror = function(error) {
347            console.error('WebSocket error:', error);
348        };
349    </script>
350</body>
351</html>`
352
353    w.Header().Set("Content-Type", "text/html")
354    fmt.Fprint(w, html)
355}
356
357func main() {
358    hub := newHub()
359    go hub.run()
360
361    http.HandleFunc("/", serveHome)
362    http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
363        serveWebSocket(hub, w, r)
364    })
365
366    port := ":8080"
367    fmt.Printf("WebSocket Chat Server starting on %s\n", port)
368    fmt.Println("Open http://localhost:8080 in your browser")
369    fmt.Println("Multiple users can join and chat in real-time!")
370
371    if err := http.ListenAndServe(port, nil); err != nil {
372        log.Fatal("ListenAndServe: ", err)
373    }
374}

Note: This exercise requires the gorilla/websocket package. Install it with:

1go get github.com/gorilla/websocket

Summary

Core HTTP Patterns

Go's net/http package provides everything needed for production web development:

Pattern Use Case Key Benefit
Basic Server Simple web services 8 lines to production server
Handler Functions Route handling Clean separation of concerns
ServeMux Router URL routing Built-in pattern matching
Middleware Cross-cutting concerns Composable request processing
JSON API REST endpoints Encode/Decode built-in
File Server Static assets Production-ready file serving
HTTP Client External APIs Retry logic, timeouts, pooling
WebSockets Real-time communication Live updates, chat, notifications

Key Takeaways

The Go Web Advantage:

  1. Batteries Included: No framework needed - net/http is production-ready
  2. Goroutine Per Request: Handle millions of concurrent connections
  3. Standard Interfaces: All middleware and tools are compatible
  4. Built-in HTTP/2: Modern protocol support out of the box
  5. Minimal Memory: ~2KB per connection vs ~100KB in other languages

HTTP Server Best Practices:

  • ✅ Always set timeouts
  • ✅ Use context for cancellation and request-scoped values
  • ✅ Handle errors explicitly - log and return appropriate status codes
  • ✅ Set proper Content-Type headers
  • ✅ Implement graceful shutdown for zero-downtime deploys
  • ✅ Use middleware for authentication, logging, CORS
  • ✅ Close response bodies in HTTP clients

Performance Optimization:

  • Connection Pooling: Reuse HTTP connections
  • Response Compression: Use gzip middleware for large responses
  • Static File Caching: Set Cache-Control headers
  • Streaming: Use io.Copy for large files
  • Rate Limiting: Prevent abuse with token bucket or leaky bucket patterns

REST API Quick Reference

 1// Read request body
 2var data RequestData
 3json.NewDecoder(r.Body).Decode(&data)
 4
 5// Write JSON response
 6w.Header().Set("Content-Type", "application/json")
 7w.WriteHeader(http.StatusOK)
 8json.NewEncoder(w).Encode(responseData)
 9
10// URL parameters
11id := chi.URLParam(r, "id")
12
13// Query parameters
14query := r.URL.Query().Get("search")
15
16// Headers
17token := r.Header.Get("Authorization")
18w.Header().Set("X-Custom-Header", "value")
19
20// Cookies
21cookie, err := r.Cookie("session")
22http.SetCookie(w, &http.Cookie{Name: "session", Value: "..."})

Middleware Pattern

 1func LoggerMiddleware(next http.Handler) http.Handler {
 2    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 3        start := time.Now()
 4        next.ServeHTTP(w, r)
 5        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
 6    })
 7}
 8
 9// Apply middleware
10handler := LoggerMiddleware(AuthMiddleware(yourHandler))

Security Checklist

Essential Security Practices:

  • ✅ Use HTTPS in production
  • ✅ Validate and sanitize all input
  • ✅ Implement authentication
  • ✅ Add CORS headers for cross-origin requests
  • ✅ Set security headers
  • ✅ Rate limit endpoints to prevent abuse
  • ✅ Use context timeouts to prevent resource exhaustion
  • ✅ Sanitize error messages

Common Security Headers:

1w.Header().Set("Content-Security-Policy", "default-src 'self'")
2w.Header().Set("X-Frame-Options", "DENY")
3w.Header().Set("X-Content-Type-Options", "nosniff")
4w.Header().Set("X-XSS-Protection", "1; mode=block")
5w.Header().Set("Strict-Transport-Security", "max-age=31536000")

HTTP Client Best Practices

 1// Production-ready HTTP client with timeouts
 2client := &http.Client{
 3    Timeout: 10 * time.Second,
 4    Transport: &http.Transport{
 5        MaxIdleConns:        100,
 6        MaxIdleConnsPerHost: 10,
 7        IdleConnTimeout:     90 * time.Second,
 8    },
 9}
10
11// Always close response bodies
12resp, err := client.Get(url)
13if err != nil {
14    return err
15}
16defer resp.Body.Close()
17
18// Check status codes
19if resp.StatusCode != http.StatusOK {
20    return fmt.Errorf("unexpected status: %d", resp.StatusCode)
21}

Performance Metrics

Go Web Server Capabilities:

Concurrent Connections: 1,000,000+
Requests per Second:    100,000+
Memory per Connection:  ~2KB
Latency:          <10ms
HTTP/2 Support:         Built-in, no configuration

Comparison with Other Languages:

Language Concurrent Conns Memory/Conn Complexity
Go 1M+ ~2KB Low
Node.js 100K ~10KB Medium
Python 10K ~50KB High
Java 50K ~100KB High

Common Patterns Cheat Sheet

 1// Health check endpoint
 2http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
 3    w.WriteHeader(http.StatusOK)
 4    json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
 5})
 6
 7// File upload
 8file, header, err := r.FormFile("file")
 9defer file.Close()
10out, _ := os.Create(header.Filename)
11defer out.Close()
12io.Copy(out, file)
13
14// Streaming response
15w.Header().Set("Content-Type", "text/event-stream")
16flusher, _ := w.(http.Flusher)
17for event := range events {
18    fmt.Fprintf(w, "data: %v\n\n", event)
19    flusher.Flush()
20}
21
22// Graceful shutdown
23server := &http.Server{Addr: ":8080"}
24go server.ListenAndServe()
25<-shutdownSignal
26ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
27defer cancel()
28server.Shutdown(ctx)

Next Steps in Your Go Journey

Web Frameworks & Advanced Patterns:

  • Web Frameworks Basics - Introduction to Go web frameworks
  • Gin & Echo - Popular lightweight frameworks
  • Fiber & Chi - Performance vs compatibility trade-offs
  • Microservices - Building distributed systems

Testing & Deployment:

  • Testing Fundamentals - Testing HTTP handlers and APIs
  • Advanced Testing - Load testing, integration tests
  • Kubernetes Operators - Deploying to Kubernetes
  • gRPC Services - High-performance RPC

Remember: Go Makes Web Development Simple

The patterns you've learned here power production systems at:

  • Netflix: Streaming billions of hours of content
  • Uber: Processing millions of rides per day
  • Cloudflare: Handling billions of HTTP requests daily
  • Dropbox: Serving petabytes of file storage

Go's web development philosophy is simple: provide excellent standard library tools that handle the common cases, and get out of your way for everything else. Master these fundamentals, and you'll build web services that scale from 10 to 10 million users without changing your architecture.