1. go
  2. /web
  3. /servers

Building Web Servers in Go

Go's net/http package provides a powerful and efficient way to build web servers. This guide covers everything you need to know about creating web servers in Go.

Basic Web Server

Simple HTTP Server

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    // Define handler function
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World!")
    })

    // Start server
    log.Println("Starting server on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Custom Handler

type Handler struct {
    // Handler configuration
    config Config
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/":
        h.handleHome(w, r)
    case "/about":
        h.handleAbout(w, r)
    default:
        http.NotFound(w, r)
    }
}

func main() {
    handler := &Handler{
        config: DefaultConfig(),
    }
    
    server := &http.Server{
        Addr:    ":8080",
        Handler: handler,
    }
    
    log.Fatal(server.ListenAndServe())
}

Server Configuration

Custom Server Settings

func NewServer(config Config) *http.Server {
    return &http.Server{
        Addr:         config.Address,
        Handler:      config.Handler,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
        TLSConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
        },
    }
}

HTTPS Support

func main() {
    server := &http.Server{
        Addr:    ":443",
        Handler: handler,
        TLSConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
        },
    }
    
    // Start HTTPS server
    log.Fatal(server.ListenAndServeTLS("cert.pem", "key.pem"))
}

Request Handling

Request Processing

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // Parse query parameters
    query := r.URL.Query()
    name := query.Get("name")
    
    // Parse form data
    if err := r.ParseForm(); err != nil {
        http.Error(w, "Failed to parse form", http.StatusBadRequest)
        return
    }
    
    // Get form value
    email := r.FormValue("email")
    
    // Set response headers
    w.Header().Set("Content-Type", "application/json")
    
    // Write response
    response := map[string]string{
        "name":  name,
        "email": email,
    }
    json.NewEncoder(w).Encode(response)
}

File Handling

func handleFileUpload(w http.ResponseWriter, r *http.Request) {
    // Parse multipart form
    if err := r.ParseMultipartForm(10 << 20); err != nil {
        http.Error(w, "File too large", 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
    dst, err := os.Create(handler.Filename)
    if err != nil {
        http.Error(w, "Error saving file", http.StatusInternalServerError)
        return
    }
    defer dst.Close()
    
    // Copy file contents
    if _, err := io.Copy(dst, file); err != nil {
        http.Error(w, "Error saving file", http.StatusInternalServerError)
        return
    }
}

Middleware Integration

Basic Middleware

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Call the next handler
        next.ServeHTTP(w, r)
        
        // Log request details
        log.Printf(
            "%s %s %s",
            r.Method,
            r.RequestURI,
            time.Since(start),
        )
    })
}

func main() {
    handler := http.HandlerFunc(handleRequest)
    
    // Wrap handler with middleware
    http.Handle("/", loggingMiddleware(handler))
    http.ListenAndServe(":8080", nil)
}

Best Practices

1. Graceful Shutdown

func main() {
    server := &http.Server{
        Addr:    ":8080",
        Handler: handler,
    }
    
    // Start server in goroutine
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("ListenAndServe(): %v", err)
        }
    }()
    
    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    
    // Shutdown server gracefully
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := server.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }
    
    log.Println("Server exiting")
}

2. Error Handling

type ErrorResponse struct {
    Error   string `json:"error"`
    Code    int    `json:"code"`
    Message string `json:"message"`
}

func handleError(w http.ResponseWriter, err error, status int) {
    response := ErrorResponse{
        Error:   http.StatusText(status),
        Code:    status,
        Message: err.Error(),
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(response)
}

3. Request Validation

func validateRequest(r *http.Request) error {
    // Validate method
    if r.Method != http.MethodPost {
        return fmt.Errorf("method %s not allowed", r.Method)
    }
    
    // Validate content type
    contentType := r.Header.Get("Content-Type")
    if contentType != "application/json" {
        return fmt.Errorf("content type %s not supported", contentType)
    }
    
    // Validate body size
    if r.ContentLength > maxBodySize {
        return fmt.Errorf("request body too large")
    }
    
    return nil
}

Security Considerations

1. CORS Configuration

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")
        
        // Handle preflight requests
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}

2. Rate Limiting

type RateLimiter struct {
    requests map[string][]time.Time
    mu       sync.Mutex
    rate     int
    window   time.Duration
}

func (rl *RateLimiter) Allow(ip string) bool {
    rl.mu.Lock()
    defer rl.mu.Unlock()
    
    now := time.Now()
    windowStart := now.Add(-rl.window)
    
    // Remove old requests
    requests := rl.requests[ip]
    for i, t := range requests {
        if t.After(windowStart) {
            requests = requests[i:]
            break
        }
    }
    
    // Check rate limit
    if len(requests) >= rl.rate {
        return false
    }
    
    // Add new request
    rl.requests[ip] = append(requests, now)
    return true
}

Next Steps