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
- Learn about Routing
- Explore Middleware
- Study Sessions