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