Building HTTP Servers in Go
Go's net/http
package provides a powerful foundation for building HTTP servers. This guide covers everything from basic servers to advanced features and best practices.
Basic HTTP Server
Simple Server
func main() {
// Define handler
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
// Start server
fmt.Println("Server starting on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
Custom Server Configuration
func customServer() error {
server := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 15 * time.Second,
Handler: nil, // Use default ServeMux
}
return server.ListenAndServe()
}
Routing
Basic Routing
func setupRoutes() {
// Root handler
http.HandleFunc("/", homeHandler)
// API routes
http.HandleFunc("/api/users", usersHandler)
http.HandleFunc("/api/products", productsHandler)
// Serve static files
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprintf(w, "Welcome Home!")
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// Handle GET
case http.MethodPost:
// Handle POST
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
Custom Router
type Router struct {
handlers map[string]http.HandlerFunc
}
func NewRouter() *Router {
return &Router{
handlers: make(map[string]http.HandlerFunc),
}
}
func (r *Router) Handle(pattern string, handler http.HandlerFunc) {
r.handlers[pattern] = handler
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if handler, ok := r.handlers[req.URL.Path]; ok {
handler(w, req)
return
}
http.NotFound(w, req)
}
Middleware
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 the request
log.Printf(
"%s %s %s",
r.Method,
r.RequestURI,
time.Since(start),
)
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Call the next handler
next.ServeHTTP(w, r)
})
}
Middleware Chain
type Middleware func(http.Handler) http.Handler
func Chain(h http.Handler, middleware ...Middleware) http.Handler {
for _, m := range middleware {
h = m(h)
}
return h
}
func main() {
handler := http.HandlerFunc(finalHandler)
// Apply middleware
chain := Chain(handler,
loggingMiddleware,
authMiddleware,
// Add more middleware...
)
http.Handle("/", chain)
}
Request Handling
Reading Request Data
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Read query parameters
name := r.URL.Query().Get("name")
// Read headers
userAgent := r.Header.Get("User-Agent")
// Read cookies
cookie, err := r.Cookie("session")
if err != nil {
http.Error(w, "No session", http.StatusBadRequest)
return
}
// Read body
var data struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
Writing Response
func writeResponse(w http.ResponseWriter, r *http.Request) {
// Set headers
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom-Header", "value")
// Set status code
w.WriteHeader(http.StatusOK)
// Write response
response := map[string]interface{}{
"status": "success",
"data": map[string]string{
"message": "Hello, World!",
},
}
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, "Error encoding response", http.StatusInternalServerError)
return
}
}
Static File Serving
Basic File Server
func setupStaticFiles() {
// Serve entire directory
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Serve single file
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/favicon.ico")
})
}
Custom File Server
func customFileServer(root string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add custom headers
w.Header().Set("Cache-Control", "max-age=3600")
// Serve file
http.FileServer(http.Dir(root)).ServeHTTP(w, r)
})
}
Graceful Shutdown
Implementing Graceful Shutdown
func main() {
server := &http.Server{
Addr: ":8080",
Handler: setupHandlers(),
}
// 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")
}
Best Practices
Use Context for Timeouts
func handleWithContext(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() select { case <-ctx.Done(): http.Error(w, "Request timeout", http.StatusGatewayTimeout) return case result := <-processRequest(ctx): json.NewEncoder(w).Encode(result) } }
Handle Panic Recovery
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: %v", err) http.Error(w, "Internal server error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }
Validate Request Size
func limitBodySize(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MB next.ServeHTTP(w, r) }) }
Use HTTPS
func main() { // Redirect HTTP to HTTPS go http.ListenAndServe(":80", http.HandlerFunc(redirect)) // HTTPS server log.Fatal(http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)) } func redirect(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently) }
Next Steps
- Learn about HTTP Client
- Explore Template Engine
- Study JSON Processing