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:
- Handler Functions - Simple functions for basic routes
- 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.
Cross-References from Other Articles
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!
Advanced Topics and Related Content
For domain-specific implementations, see:
- Real-World Applications - Web frameworks, GraphQL servers, microservices
- Cloud Native Go - Kubernetes deployments, Docker containers, serverless functions
- Testing & Quality - Web API testing, contract testing, load testing
Learning Path
- Start here - Master basic HTTP patterns in this comprehensive guide
- Explore frameworks - See Real-World Applications for framework-specific patterns
- Deploy to cloud - Check Cloud Native Go for production deployment
- 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:
- Batteries Included: No framework needed -
net/httpis production-ready - Goroutine Per Request: Handle millions of concurrent connections
- Standard Interfaces: All middleware and tools are compatible
- Built-in HTTP/2: Modern protocol support out of the box
- 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.Copyfor 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.