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