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