Custom HTTP Server

Exercise: Custom HTTP Server

Difficulty - Intermediate

📖 Background: This exercise builds upon the comprehensive HTTP server coverage in Web Development. Instead of reimplementing basic HTTP patterns, you'll focus on building a custom framework with advanced middleware and routing capabilities.

Learning Objectives

  • Build custom HTTP servers with middleware
  • Implement routing and request handling
  • Add logging, authentication, and CORS
  • Handle graceful shutdown
  • Implement rate limiting and metrics

Problem Statement

Create a custom HTTP server framework with middleware support, routing, and common utilities.

Core Components

 1package httpserver
 2
 3import (
 4    "context"
 5    "net/http"
 6    "time"
 7)
 8
 9type Handler func(http.ResponseWriter, *http.Request) error
10type Middleware func(Handler) Handler
11
12type Server struct {
13    router     *Router
14    middleware []Middleware
15    server     *http.Server
16}
17
18type Router struct {
19    routes map[string]map[string]Handler // method -> path -> handler
20}
21
22func NewServer(addr string) *Server
23func Use(mw Middleware)
24func GET(path string, handler Handler)
25func POST(path string, handler Handler)
26func Start() error
27func Shutdown(ctx context.Context) error
28
29// Middleware builders
30func LoggingMiddleware() Middleware
31func RecoveryMiddleware() Middleware
32func CORSMiddleware(origins []string) Middleware
33func AuthMiddleware(validateToken func(string) bool) Middleware

Solution

Click to see the solution
  1package httpserver
  2
  3import (
  4    "context"
  5    "fmt"
  6    "log"
  7    "net/http"
  8    "strings"
  9    "time"
 10)
 11
 12type Handler func(http.ResponseWriter, *http.Request) error
 13type Middleware func(Handler) Handler
 14
 15type Server struct {
 16    router     *Router
 17    middleware []Middleware
 18    server     *http.Server
 19}
 20
 21type Router struct {
 22    routes map[string]map[string]Handler
 23}
 24
 25func NewRouter() *Router {
 26    return &Router{
 27        routes: make(map[string]map[string]Handler),
 28    }
 29}
 30
 31func Register(method, path string, handler Handler) {
 32    if r.routes[method] == nil {
 33        r.routes[method] = make(map[string]Handler)
 34    }
 35    r.routes[method][path] = handler
 36}
 37
 38func ServeHTTP(w http.ResponseWriter, req *http.Request) {
 39    if handlers, ok := r.routes[req.Method]; ok {
 40        if handler, ok := handlers[req.URL.Path]; ok {
 41            if err := handler(w, req); err != nil {
 42                http.Error(w, err.Error(), http.StatusInternalServerError)
 43            }
 44            return
 45        }
 46    }
 47    http.NotFound(w, req)
 48}
 49
 50func NewServer(addr string) *Server {
 51    router := NewRouter()
 52    return &Server{
 53        router:     router,
 54        middleware: []Middleware{},
 55        server: &http.Server{
 56            Addr:         addr,
 57            Handler:      router,
 58            ReadTimeout:  10 * time.Second,
 59            WriteTimeout: 10 * time.Second,
 60        },
 61    }
 62}
 63
 64func Use(mw Middleware) {
 65    s.middleware = append(s.middleware, mw)
 66}
 67
 68func GET(path string, handler Handler) {
 69    s.register("GET", path, handler)
 70}
 71
 72func POST(path string, handler Handler) {
 73    s.register("POST", path, handler)
 74}
 75
 76func register(method, path string, handler Handler) {
 77    // Apply middleware in reverse order
 78    for i := len(s.middleware) - 1; i >= 0; i-- {
 79        handler = s.middleware[i](handler)
 80    }
 81    s.router.Register(method, path, handler)
 82}
 83
 84func Start() error {
 85    log.Printf("Starting server on %s", s.server.Addr)
 86    return s.server.ListenAndServe()
 87}
 88
 89func Shutdown(ctx context.Context) error {
 90    log.Println("Shutting down server...")
 91    return s.server.Shutdown(ctx)
 92}
 93
 94// Middleware implementations
 95
 96func LoggingMiddleware() Middleware {
 97    return func(next Handler) Handler {
 98        return func(w http.ResponseWriter, r *http.Request) error {
 99            start := time.Now()
100            log.Printf("[%s] %s", r.Method, r.URL.Path)
101            err := next(w, r)
102            log.Printf("[%s] %s - %v", r.Method, r.URL.Path, time.Since(start))
103            return err
104        }
105    }
106}
107
108func RecoveryMiddleware() Middleware {
109    return func(next Handler) Handler {
110        return func(w http.ResponseWriter, r *http.Request) error {
111            defer func() {
112                if err := recover(); err != nil {
113                    log.Printf("Panic recovered: %v", err)
114                    http.Error(w, "Internal Server Error", http.StatusInternalServerError)
115                }
116            }()
117            return next(w, r)
118        }
119    }
120}
121
122func CORSMiddleware(origins []string) Middleware {
123    return func(next Handler) Handler {
124        return func(w http.ResponseWriter, r *http.Request) error {
125            origin := r.Header.Get("Origin")
126            for _, allowed := range origins {
127                if origin == allowed {
128                    w.Header().Set("Access-Control-Allow-Origin", origin)
129                    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
130                    w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
131                    break
132                }
133            }
134
135            if r.Method == "OPTIONS" {
136                w.WriteHeader(http.StatusOK)
137                return nil
138            }
139
140            return next(w, r)
141        }
142    }
143}
144
145func AuthMiddleware(validateToken func(string) bool) Middleware {
146    return func(next Handler) Handler {
147        return func(w http.ResponseWriter, r *http.Request) error {
148            auth := r.Header.Get("Authorization")
149            if !strings.HasPrefix(auth, "Bearer ") {
150                http.Error(w, "Unauthorized", http.StatusUnauthorized)
151                return nil
152            }
153
154            token := strings.TrimPrefix(auth, "Bearer ")
155            if !validateToken(token) {
156                http.Error(w, "Unauthorized", http.StatusUnauthorized)
157                return nil
158            }
159
160            return next(w, r)
161        }
162    }
163}

Key Takeaways

  • Middleware chains enable composable request processing
  • Routers map paths to handlers
  • Graceful shutdown prevents data loss
  • Context enables request cancellation
  • Timeouts prevent resource exhaustion