🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Примеры Web Специфических Функций Web Go
Примеры web специфических функций Web Go включая обработку маршрутов, middleware и обслуживание статических файлов
💻 Обслуживание Статических Файлов go
🟢 simple
⭐⭐⭐
Обслуживание статических файлов, CSS, JavaScript, изображений с соответствующим кэшированием и заголовками
⏱️ 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 ===")
}
💻 Обработка Маршрутов go
🟡 intermediate
⭐⭐⭐
URL маршрутизация и парсинг используя Go http пакет и популярные роутеры как 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 обработки запросов для логирования, аутентификации, CORS и кастомных обработчиков
⏱️ 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 ===")
}