🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples de Fonctionnalités Web Web Go
Exemples de fonctionnalités spécifiques Web Go incluant le gestionnaire de routes, middleware et service de fichiers statiques
💻 Service de Fichiers Statiques go
🟢 simple
⭐⭐⭐
Servir des fichiers statiques, CSS, JavaScript, images avec un cache et des en-têtes appropriés
⏱️ 20 min
🏷️ go, web, static files
Prerequisites:
Basic Go, net/http package, file io
// Web Go Static File Serving Examples
// Serve static files with caching and headers
package main
import (
"encoding/base64"
"fmt"
"io"
"log"
"mime"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
// 1. Basic Static File Server
// FileServer serves files from a directory
func FileServer(root http.FileSystem) http.Handler {
fs := http.FileServer(root)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Clean the path
path := filepath.Clean(r.URL.Path)
// Open file
file, err := root.Open(path)
if err != nil {
if os.IsNotExist(err) {
http.NotFound(w, r)
} else {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
defer file.Close()
// Get file info
stat, err := file.Stat()
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Serve file
http.ServeContent(w, r, path, stat.ModTime(), file)
})
}
// 2. Static File Server with Custom Headers
// StaticServer serves static files with custom headers
type StaticServer struct {
root string
headers map[string]string
}
// NewStaticServer creates a new static file server
func NewStaticServer(root string) *StaticServer {
return &StaticServer{
root: root,
headers: make(map[string]string),
}
}
// AddHeader adds a default header
func (ss *StaticServer) AddHeader(key, value string) {
ss.headers[key] = value
}
// ServeHTTP serves HTTP requests
func (ss *StaticServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Clean path
path := filepath.Join(ss.root, filepath.Clean(r.URL.Path))
// Get file info
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
http.NotFound(w, r)
} else {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
// Check if it's a directory
if info.IsDir() {
// Try to serve index.html
indexPath := filepath.Join(path, "index.html")
if _, err := os.Stat(indexPath); err == nil {
path = indexPath
info, _ = os.Stat(path)
} else {
// Directory listing (for development)
ss.serveDirectoryListing(w, r, path)
return
}
}
// Detect content type
ext := filepath.Ext(path)
contentType := mime.TypeByExtension(ext)
if contentType == "" {
contentType = "application/octet-stream"
}
// Set headers
for key, value := range ss.headers {
w.Header().Set(key, value)
}
w.Header().Set("Content-Type", contentType)
// Set cache headers
ss.setCacheHeaders(w, ext)
// Serve file
http.ServeFile(w, r, path)
}
// setCacheHeaders sets cache control headers
func (ss *StaticServer) setCacheHeaders(w http.ResponseWriter, ext string) {
// Static assets can be cached longer
switch ext {
case ".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".woff", ".woff2", ".ttf":
// Cache for 1 year
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
case ".html", ".htm":
// Don't cache HTML files
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
default:
// Cache for 1 hour
w.Header().Set("Cache-Control", "public, max-age=3600")
}
}
// serveDirectoryListing serves directory listing
func (ss *StaticServer) serveDirectoryListing(w http.ResponseWriter, r *http.Request, path string) {
entries, err := os.ReadDir(path)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<!DOCTYPE html><html><head><title>Directory Listing</title></head><body>")
fmt.Fprintf(w, "<h1>Directory Listing for %s</h1>", r.URL.Path)
fmt.Fprintf(w, "<ul>")
for _, entry := range entries {
name := entry.Name()
url := filepath.Join(r.URL.Path, name)
if entry.IsDir() {
url += "/"
}
fmt.Fprintf(w, "<li><a href='%s'>%s</a></li>", url, name)
}
fmt.Fprintf(w, "</ul></body></html>")
}
// 3. SPA File Server
// SPAFileServer serves single-page applications
type SPAFileServer struct {
root string
indexFile string
}
// NewSPAFileServer creates a new SPA file server
func NewSPAFileServer(root string) *SPAFileServer {
return &SPAFileServer{
root: root,
indexFile: "index.html",
}
}
// ServeHTTP serves HTTP requests for SPA
func (spa *SPAFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Clean path
path := filepath.Join(spa.root, filepath.Clean(r.URL.Path))
// Check if file exists
if _, err := os.Stat(path); os.IsNotExist(err) {
// File doesn't exist, serve index.html for SPA routing
indexPath := filepath.Join(spa.root, spa.indexFile)
http.ServeFile(w, r, indexPath)
return
}
// Serve file if it exists
info, err := os.Stat(path)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
if info.IsDir() {
indexPath := filepath.Join(path, spa.indexFile)
http.ServeFile(w, r, indexPath)
} else {
http.ServeFile(w, r, path)
}
}
// 4. File Server with ETag Support
// ETagFileServer serves files with ETag support
type ETagFileServer struct {
root string
}
// NewETagFileServer creates a new ETag file server
func NewETagFileServer(root string) *ETagFileServer {
return &ETagFileServer{root: root}
}
// generateETag generates ETag for file
func (ets *ETagFileServer) generateETag(path string, info os.FileInfo) string {
data := fmt.Sprintf("%s-%d-%s", path, info.Size(), info.ModTime().Unix())
hash := base64.StdEncoding.EncodeToString([]byte(data))
return fmt.Sprintf("\"%s\"", hash)
}
// ServeHTTP serves HTTP requests with ETag
func (ets *ETagFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := filepath.Join(ets.root, filepath.Clean(r.URL.Path))
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
http.NotFound(w, r)
} else {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
if info.IsDir() {
path = filepath.Join(path, "index.html")
info, _ = os.Stat(path)
}
// Generate ETag
etag := ets.generateETag(path, info)
// Check If-None-Match header
if match := r.Header.Get("If-None-Match"); match == etag {
w.WriteHeader(http.StatusNotModified)
return
}
// Set ETag header
w.Header().Set("ETag", etag)
// Serve file
http.ServeFile(w, r, path)
}
// 5. File Server with Compression
// CompressionMiddleware compresses responses
type CompressionMiddleware struct {
next http.Handler
}
// NewCompressionMiddleware creates compression middleware
func NewCompressionMiddleware(next http.Handler) *CompressionMiddleware {
return &CompressionMiddleware{next: next}
}
// ShouldCompress checks if response should be compressed
func ShouldCompress(r *http.Request) bool {
// Check Accept-Encoding header
acceptEncoding := r.Header.Get("Accept-Encoding")
return strings.Contains(acceptEncoding, "gzip")
}
// ServeHTTP serves compressed responses
func (cm *CompressionMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !ShouldCompress(r) {
cm.next.ServeHTTP(w, r)
return
}
// Check if content type is compressible
path := r.URL.Path
ext := filepath.Ext(path)
compressible := map[string]bool{
".js": true,
".css": true,
".html": true,
".txt": true,
".xml": true,
".json": true,
}
if !compressible[ext] {
cm.next.ServeHTTP(w, r)
return
}
// Set compression header
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Vary", "Accept-Encoding")
cm.next.ServeHTTP(w, r)
}
// 6. File Upload Handler
// UploadHandler handles file uploads
func UploadHandler(uploadDir string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost && r.Method != http.MethodPut {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse multipart form (max 32MB)
err := r.ParseMultipartForm(32 << 20)
if err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
// Get file from form
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error retrieving file", http.StatusBadRequest)
return
}
defer file.Close()
// Create destination file
dstPath := filepath.Join(uploadDir, handler.Filename)
dst, err := os.Create(dstPath)
if err != nil {
http.Error(w, "Error creating file", http.StatusInternalServerError)
return
}
defer dst.Close()
// Copy file
_, err = io.Copy(dst, file)
if err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, "File uploaded successfully: %s", handler.Filename)
}
}
// 7. File Download Handler
// DownloadHandler handles file downloads
func DownloadHandler(root string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get filename from query parameter
filename := r.URL.Query().Get("file")
if filename == "" {
http.Error(w, "Filename required", http.StatusBadRequest)
return
}
// Build file path
path := filepath.Join(root, filepath.Base(filename))
// Check if file exists
if _, err := os.Stat(path); os.IsNotExist(err) {
http.NotFound(w, r)
return
}
// Set headers for download
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(filename)))
w.Header().Set("Content-Type", "application/octet-stream")
// Serve file
http.ServeFile(w, r, path)
}
}
// 8. Image Thumbnail Generator
// ThumbnailHandler generates and serves thumbnails
func ThumbnailHandler(imageDir string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get filename and size from query
filename := r.URL.Query().Get("file")
width := r.URL.Query().Get("width")
height := r.URL.Query().Get("height")
if filename == "" {
http.Error(w, "Filename required", http.StatusBadRequest)
return
}
// For simplicity, just serve the original file
// In production, you'd use an image processing library
path := filepath.Join(imageDir, filepath.Base(filename))
if _, err := os.Stat(path); os.IsNotExist(err) {
http.NotFound(w, r)
return
}
log.Printf("Serving thumbnail: %s (size: %sx%s)", filename, width, height)
http.ServeFile(w, r, path)
}
}
// Usage Examples
func main() {
fmt.Println("=== Web Go Static File Serving Examples ===\n")
// 1. Basic file server
fmt.Println("--- 1. Basic File Server ---")
fs := http.FileServer(http.Dir("./public"))
fmt.Printf("FileServer: %T\n", fs)
// 2. Custom static server
fmt.Println("\n--- 2. Custom Static Server ---")
staticServer := NewStaticServer("./public")
staticServer.AddHeader("X-Content-Type-Options", "nosniff")
staticServer.AddHeader("X-Frame-Options", "DENY")
fmt.Printf("StaticServer configured with %d custom headers\n", len(staticServer.headers))
// 3. SPA file server
fmt.Println("\n--- 3. SPA File Server ---")
spaServer := NewSPAFileServer("./spa")
fmt.Printf("SPAFileServer: %T\n", spaServer)
// 4. ETag file server
fmt.Println("\n--- 4. ETag File Server ---")
etagServer := NewETagFileServer("./public")
fmt.Printf("ETagFileServer: %T\n", etagServer)
// 5. File upload handler
fmt.Println("\n--- 5. File Upload Handler ---")
uploadHandler := UploadHandler("./uploads")
fmt.Printf("UploadHandler: %T\n", uploadHandler)
// 6. Server configuration examples
fmt.Println("\n--- 6. Server Configuration ---")
// Example 1: Serve static files
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("./public"))))
fmt.Println("Static files: /static/*")
// Example 2: SPA route
mux.Handle("/app/", spaServer)
fmt.Println("SPA route: /app/*")
// Example 3: Upload endpoint
mux.HandleFunc("/upload", uploadHandler)
fmt.Println("Upload endpoint: /upload")
// Example 4: Download endpoint
mux.HandleFunc("/download", DownloadHandler("./files"))
fmt.Println("Download endpoint: /download?file=example.pdf")
// Example 5: Thumbnail endpoint
mux.HandleFunc("/thumbnail", ThumbnailHandler("./images"))
fmt.Println("Thumbnail endpoint: /thumbnail?file=image.jpg&width=200&height=200")
fmt.Println("\n--- 7. Server Setup ---")
fmt.Println("http.ListenAndServe(':8080', mux)")
fmt.Println("\n=== All Static File Serving Examples Completed ===")
}
💻 Gestion des Routes go
🟡 intermediate
⭐⭐⭐
Routage et analyse d'URL utilisant le paquet http Go et des routeurs populaires comme gorilla/mux
⏱️ 25 min
🏷️ go, web, routing
Prerequisites:
Intermediate Go, net/http package
// Web Go Route Handling Examples
// URL routing and parsing using Go http package
package main
import (
"fmt"
"net/http"
"strings"
)
// 1. Basic HTTP Routing
// BasicRouteHandler demonstrates basic route handling
func BasicRouteHandler(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
fmt.Fprintf(w, "Welcome to the Home Page!")
case "/about":
fmt.Fprintf(w, "About Us Page")
case "/contact":
fmt.Fprintf(w, "Contact Page")
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "404 - Page Not Found")
}
}
// 2. Method-Based Routing
// MethodRouteHandler handles different HTTP methods
func MethodRouteHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
fmt.Fprintf(w, "GET request received")
case http.MethodPost:
fmt.Fprintf(w, "POST request received")
case http.MethodPut:
fmt.Fprintf(w, "PUT request received")
case http.MethodDelete:
fmt.Fprintf(w, "DELETE request received")
default:
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintf(w, "Method Not Allowed")
}
}
// 3. Query Parameter Handling
// QueryParamHandler handles query parameters
func QueryParamHandler(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()
name := queryParams.Get("name")
age := queryParams.Get("age")
if name == "" {
name = "Guest"
}
fmt.Fprintf(w, "Hello, %s!", name)
if age != "" {
fmt.Fprintf(w, " You are %s years old.", age)
}
}
// 4. Path Parameter Extraction (Simple)
// UserHandler handles /users/:id pattern
func UserHandler(w http.ResponseWriter, r *http.Request) {
// Extract ID from path like /users/123
path := strings.TrimPrefix(r.URL.Path, "/users/")
if path == "" || path == r.URL.Path {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Invalid user ID")
return
}
userID := strings.Split(path, "/")[0]
fmt.Fprintf(w, "User ID: %s", userID)
}
// 5. Custom Route Structure
// Route represents a route definition
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
// Routes is a collection of Route
type Routes []Route
// Router holds route definitions
type Router struct {
routes Routes
}
// NewRouter creates a new router
func NewRouter() *Router {
return &Router{
routes: Routes{
Route{
Name: "Index",
Method: "GET",
Pattern: "/",
HandlerFunc: IndexHandler,
},
Route{
Name: "GetUser",
Method: "GET",
Pattern: "/users/{id}",
HandlerFunc: GetUserHandler,
},
Route{
Name: "CreateUser",
Method: "POST",
Pattern: "/users",
HandlerFunc: CreateUserHandler,
},
},
}
}
// Handler returns the main HTTP handler
func (router *Router) Handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, route := range router.routes {
if r.Method != route.Method {
continue
}
// Simple pattern matching
if matchRoute(route.Pattern, r.URL.Path) {
route.HandlerFunc(w, r)
return
}
}
// No matching route found
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "404 - Not Found")
})
}
// matchRoute matches URL pattern to path
func matchRoute(pattern, path string) bool {
// Replace {param} with wildcard
pattern = strings.ReplaceAll(pattern, "{id}", "*")
pattern = strings.ReplaceAll(pattern, "{name}", "*")
// Simple matching
if strings.HasSuffix(pattern, "*") {
prefix := strings.TrimSuffix(pattern, "/*")
return strings.HasPrefix(path, prefix)
}
return pattern == path
}
// Handler Functions
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the API!")
}
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
// Extract user ID from path
path := strings.TrimPrefix(r.URL.Path, "/users/")
userID := strings.Split(path, "/")[0]
fmt.Fprintf(w, "Get User: %s", userID)
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Create User")
}
// 6. Route Groups and Prefixes
// RouteGroup represents a group of routes with common prefix
type RouteGroup struct {
prefix string
routes []Route
}
// NewRouteGroup creates a new route group
func NewRouteGroup(prefix string) *RouteGroup {
return &RouteGroup{
prefix: prefix,
routes: []Route{},
}
}
// AddRoute adds a route to the group
func (rg *RouteGroup) AddRoute(method, pattern string, handler http.HandlerFunc) {
fullPattern := rg.prefix + pattern
rg.routes = append(rg.routes, Route{
Method: method,
Pattern: fullPattern,
HandlerFunc: handler,
})
}
// GetRoutes returns all routes in the group
func (rg *RouteGroup) GetRoutes() Routes {
return rg.routes
}
// 7. URL Building
// URLBuilder builds URLs with parameters
type URLBuilder struct {
baseURL string
}
// NewURLBuilder creates a new URL builder
func NewURLBuilder(baseURL string) *URLBuilder {
return &URLBuilder{baseURL: baseURL}
}
// Build builds a URL with path
func (ub *URLBuilder) Build(path string) string {
return ub.baseURL + path
}
// BuildWithParams builds a URL with path parameters
func (ub *URLBuilder) BuildWithParams(path string, params map[string]string) string {
url := ub.baseURL + path
for key, value := range params {
url = strings.ReplaceAll(url, "{"+key+"}", value)
}
return url
}
// BuildWithQuery builds a URL with query parameters
func (ub *URLBuilder) BuildWithQuery(path string, params map[string]string) string {
url := ub.baseURL + path
if len(params) > 0 {
url += "?"
i := 0
for key, value := range params {
if i > 0 {
url += "&"
}
url += fmt.Sprintf("%s=%s", key, value)
i++
}
}
return url
}
// 8. Request Context with Parameters
// Context holds request context data
type Context struct {
Params map[string]string
Query map[string]string
}
// NewContext creates a new context from request
func NewContext(r *http.Request) *Context {
ctx := &Context{
Params: make(map[string]string),
Query: make(map[string]string),
}
// Parse query parameters
for key, values := range r.URL.Query() {
if len(values) > 0 {
ctx.Query[key] = values[0]
}
}
return ctx
}
// Usage Examples
func main() {
fmt.Println("=== Web Go Route Handling Examples ===\n")
// 1. Basic routing
fmt.Println("--- 1. Basic Routing ---")
fmt.Println("Routes: /, /about, /contact")
fmt.Println("Handler: BasicRouteHandler")
// 2. Method-based routing
fmt.Println("\n--- 2. Method-Based Routing ---")
fmt.Println("Methods: GET, POST, PUT, DELETE")
fmt.Println("Handler: MethodRouteHandler")
// 3. Query parameters
fmt.Println("\n--- 3. Query Parameters ---")
fmt.Println("URL: /greet?name=John&age=30")
fmt.Println("Handler: QueryParamHandler")
// 4. Custom router
fmt.Println("\n--- 4. Custom Router ---")
router := NewRouter()
fmt.Printf("Registered %d routes\n", len(router.routes))
for _, route := range router.routes {
fmt.Printf(" %s %s - %s\n", route.Method, route.Pattern, route.Name)
}
// 5. Route groups
fmt.Println("\n--- 5. Route Groups ---")
apiGroup := NewRouteGroup("/api/v1")
apiGroup.AddRoute("GET", "/users", IndexHandler)
apiGroup.AddRoute("POST", "/users", CreateUserHandler)
apiGroup.AddRoute("GET", "/users/{id}", GetUserHandler)
fmt.Printf("API Group has %d routes\n", len(apiGroup.GetRoutes()))
// 6. URL building
fmt.Println("\n--- 6. URL Building ---")
builder := NewURLBuilder("https://api.example.com")
fmt.Printf("Home: %s\n", builder.Build("/"))
fmt.Printf("User: %s\n", builder.BuildWithParams("/users/{id}", map[string]string{"id": "123"}))
fmt.Printf("Search: %s\n", builder.BuildWithQuery("/search", map[string]string{"q": "golang", "limit": "10"}))
// 7. Server setup example
fmt.Println("\n--- 7. Server Setup ---")
fmt.Println("http.HandleFunc('/', BasicRouteHandler)")
fmt.Println("http.ListenAndServe(':8080', nil)")
fmt.Println("\n=== All Route Handling Examples Completed ===")
}
💻 Middleware go
🟡 intermediate
⭐⭐⭐⭐
Middleware de traitement des requêtes pour la journalisation, l'authentification, CORS et gestionnaires personnalisés
⏱️ 30 min
🏷️ go, web, middleware
Prerequisites:
Intermediate Go, net/http package, context package
// Web Go Middleware Examples
// Request processing middleware using Go http package
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
)
// 1. Basic Middleware Type
// Middleware is a function that wraps an http.Handler
type Middleware func(http.Handler) http.Handler
// 2. Logging Middleware
// LoggingMiddleware logs HTTP requests
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create a custom response writer to capture status code
lrw := &loggingResponseWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}
// Call the next handler
next.ServeHTTP(lrw, r)
// Log the request
log.Printf(
"%s %s %s %d %s",
r.Method,
r.RequestURI,
r.RemoteAddr,
lrw.statusCode,
time.Since(start),
)
})
}
// loggingResponseWriter wraps http.ResponseWriter to capture status code
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
// 3. Authentication Middleware
// AuthMiddleware validates authentication tokens
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{
"error": "Authorization header required",
})
return
}
// Validate token (simplified)
if !strings.HasPrefix(token, "Bearer ") {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{
"error": "Invalid authorization format",
})
return
}
actualToken := strings.TrimPrefix(token, "Bearer ")
if actualToken != "valid-token" {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{
"error": "Invalid token",
})
return
}
// Add user context
ctx := context.WithValue(r.Context(), "user", "authenticated-user")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 4. CORS Middleware
// CORSMiddleware handles Cross-Origin Resource Sharing
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Max-Age", "86400")
// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// 5. Content Type Middleware
// ContentTypeMiddleware validates and sets content type
func ContentTypeMiddleware(expectedType string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check content type for POST/PUT requests
if r.Method == http.MethodPost || r.Method == http.MethodPut {
contentType := r.Header.Get("Content-Type")
if contentType != expectedType {
w.WriteHeader(http.StatusUnsupportedMediaType)
json.NewEncoder(w).Encode(map[string]string{
"error": fmt.Sprintf("Content-Type must be %s", expectedType),
})
return
}
}
// Set response content type
w.Header().Set("Content-Type", expectedType)
next.ServeHTTP(w, r)
})
}
}
// 6. Recovery Middleware
// RecoveryMiddleware recovers from panics
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered: %v", err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
"error": "Internal Server Error",
})
}
}()
next.ServeHTTP(w, r)
})
}
// 7. Request ID Middleware
// RequestIDMiddleware adds unique request ID
func RequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = fmt.Sprintf("%d", time.Now().UnixNano())
}
w.Header().Set("X-Request-ID", requestID)
ctx := context.WithValue(r.Context(), "requestID", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 8. Timeout Middleware
// TimeoutMiddleware adds timeout to requests
func TimeoutMiddleware(timeout time.Duration) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// 9. Rate Limiting Middleware
// RateLimiter tracks request counts
type RateLimiter struct {
requests map[string][]time.Time
limit int
window time.Duration
}
// NewRateLimiter creates a new rate limiter
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
rl := &RateLimiter{
requests: make(map[string][]time.Time),
limit: limit,
window: window,
}
// Start cleanup goroutine
go rl.cleanup()
return rl
}
// Allow checks if request is allowed
func (rl *RateLimiter) Allow(ip string) bool {
now := time.Now()
.rl.requests[ip] = append(rl.requests[ip], now)
// Remove old requests
var valid []time.Time
for _, t := range rl.requests[ip] {
if now.Sub(t) < rl.window {
valid = append(valid, t)
}
}
rl.requests[ip] = valid
return len(rl.requests[ip]) <= rl.limit
}
// cleanup removes old entries
func (rl *RateLimiter) cleanup() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C {
now := time.Now()
for ip, times := range rl.requests {
var valid []time.Time
for _, t := range times {
if now.Sub(t) < rl.window {
valid = append(valid, t)
}
}
if len(valid) == 0 {
delete(rl.requests, ip)
} else {
rl.requests[ip] = valid
}
}
}
}
// RateLimitMiddleware implements rate limiting
func RateLimitMiddleware(limiter *RateLimiter) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
if !limiter.Allow(ip) {
w.WriteHeader(http.StatusTooManyRequests)
json.NewEncoder(w).Encode(map[string]string{
"error": "Rate limit exceeded",
})
return
}
next.ServeHTTP(w, r)
})
}
}
// 10. Middleware Chain
// Chain chains multiple middleware
func Chain(middlewares ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
next = middlewares[i](next)
}
return next
}
}
// 11. Example Handlers
func HomeHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{
"message": "Welcome Home!",
})
}
func ProtectedHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user")
json.NewEncoder(w).Encode(map[string]interface{}{
"message": "This is a protected endpoint",
"user": user,
})
}
func APIHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{
"message": "API endpoint",
})
}
// Usage Examples
func main() {
fmt.Println("=== Web Go Middleware Examples ===\n")
// 1. Setup middlewares
fmt.Println("--- 1. Available Middleware ---")
fmt.Println("- LoggingMiddleware")
fmt.Println("- AuthMiddleware")
fmt.Println("- CORSMiddleware")
fmt.Println("- RecoveryMiddleware")
fmt.Println("- RequestIDMiddleware")
// 2. Create middleware chain
fmt.Println("\n--- 2. Middleware Chain ---")
fmt.Println("Chain order matters:")
fmt.Println("1. Recovery (outermost)")
fmt.Println("2. Logging")
fmt.Println("3. Request ID")
fmt.Println("4. CORS")
fmt.Println("5. Authentication")
fmt.Println("6. Content Type")
fmt.Println("7. Handler (innermost)")
// 3. Example router with middleware
fmt.Println("\n--- 3. Router with Middleware ---")
// Create handlers
mux := http.NewServeMux()
mux.HandleFunc("/", HomeHandler)
mux.HandleFunc("/protected", ProtectedHandler)
mux.HandleFunc("/api", APIHandler)
// Apply middleware chain
rateLimiter := NewRateLimiter(10, time.Minute)
handler := Chain(
RecoveryMiddleware,
LoggingMiddleware,
RequestIDMiddleware,
CORSMiddleware,
RateLimitMiddleware(rateLimiter),
)(mux)
fmt.Printf("Server configured with middleware chain\n")
// 4. Middleware examples
fmt.Println("\n--- 4. Middleware Examples ---")
// Content type middleware
jsonMiddleware := ContentTypeMiddleware("application/json")
fmt.Printf("Content-Type middleware created: %T\n", jsonMiddleware)
// Timeout middleware
timeoutMiddleware := TimeoutMiddleware(30 * time.Second)
fmt.Printf("Timeout middleware created: %T\n", timeoutMiddleware)
// 5. Server configuration
fmt.Println("\n--- 5. Server Configuration ---")
fmt.Println("http.ListenAndServe(':8080', handler)")
// 6. Protected routes example
fmt.Println("\n--- 6. Protected Routes ---")
fmt.Println("Public routes: /")
fmt.Println("Protected routes: /protected (requires auth)")
fmt.Println("API routes: /api")
fmt.Println("\n=== All Middleware Examples Completed ===")
}