Building HTTP Handlers in Go
HTTP handlers are the core of web applications in Go. The standard library provides everything you need to build robust web services. This guide covers everything about building effective HTTP handlers.
Basic HTTP Handlers
Simple Handler
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Handler with Response Headers
package main
import (
"fmt"
"log"
"net/http"
)
func jsonHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"message": "Hello, World!"}`)
}
func main() {
http.HandleFunc("/json", jsonHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Handler with Status Codes
package main
import (
"fmt"
"log"
"net/http"
)
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Page not found")
}
func createdHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, "Resource created")
}
func main() {
http.HandleFunc("/notfound", notFoundHandler)
http.HandleFunc("/created", createdHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Request Handling
Reading Request Data
package main
import (
"fmt"
"io"
"log"
"net/http"
)
func echoHandler(w http.ResponseWriter, r *http.Request) {
// Read request body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
defer r.Body.Close()
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "You sent: %s", string(body))
}
func main() {
http.HandleFunc("/echo", echoHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Query Parameters
package main
import (
"fmt"
"log"
"net/http"
)
func searchHandler(w http.ResponseWriter, r *http.Request) {
// Parse query parameters
query := r.URL.Query()
// Get single parameter
search := query.Get("q")
fmt.Fprintf(w, "Search query: %s\n", search)
// Get all parameters
for key, values := range query {
fmt.Fprintf(w, "%s: %v\n", key, values)
}
}
func main() {
http.HandleFunc("/search", searchHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Form Data
package main
import (
"fmt"
"log"
"net/http"
)
func formHandler(w http.ResponseWriter, r *http.Request) {
// Parse form data
err := r.ParseForm()
if err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
// Get form values
name := r.FormValue("name")
email := r.FormValue("email")
fmt.Fprintf(w, "Name: %s\nEmail: %s\n", name, email)
}
func main() {
http.HandleFunc("/form", formHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Request Headers
package main
import (
"fmt"
"log"
"net/http"
)
func headerHandler(w http.ResponseWriter, r *http.Request) {
// Get specific header
userAgent := r.Header.Get("User-Agent")
fmt.Fprintf(w, "User-Agent: %s\n", userAgent)
// Get all headers
fmt.Fprintf(w, "\nAll headers:\n")
for key, values := range r.Header {
for _, value := range values {
fmt.Fprintf(w, "%s: %s\n", key, value)
}
}
}
func main() {
http.HandleFunc("/headers", headerHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Handler Types and Patterns
Handler Interface
package main
import (
"fmt"
"log"
"net/http"
)
// Custom handler type
type MyHandler struct {
message string
}
// Implement http.Handler interface
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, h.message)
}
func main() {
handler := &MyHandler{message: "Custom handler"}
http.Handle("/custom", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Handler Chaining
package main
import (
"fmt"
"log"
"net/http"
)
// Middleware function
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Request: %s %s\n", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}
func main() {
handler := http.HandlerFunc(helloHandler)
http.Handle("/", loggingMiddleware(handler))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Middleware
Authentication Middleware
package main
import (
"fmt"
"log"
"net/http"
)
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, "Missing authorization token", http.StatusUnauthorized)
return
}
if token != "Bearer valid-token" {
http.Error(w, "Invalid token", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is protected content")
}
func main() {
handler := http.HandlerFunc(protectedHandler)
http.Handle("/protected", authMiddleware(handler))
log.Fatal(http.ListenAndServe(":8080", nil))
}
CORS Middleware
package main
import (
"fmt"
"log"
"net/http"
)
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"status": "ok"}`)
}
func main() {
handler := http.HandlerFunc(apiHandler)
http.Handle("/api", corsMiddleware(handler))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Practical Examples
RESTful API Handler
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = map[int]User{
1: {ID: 1, Name: "Alice", Email: "[email protected]"},
2: {ID: 2, Name: "Bob", Email: "[email protected]"},
}
func userHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Extract ID from path
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 3 {
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
// Get all users
json.NewEncoder(w).Encode(users)
case http.MethodPost:
// Create user
var user User
json.NewDecoder(r.Body).Decode(&user)
users[user.ID] = user
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func main() {
http.HandleFunc("/users", userHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
File Upload Handler
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse multipart form
err := r.ParseMultipartForm(10 << 20) // 10MB max
if err != nil {
http.Error(w, "Error parsing form", 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 creating file", http.StatusInternalServerError)
return
}
defer dst.Close()
// Copy file
_, err = io.Copy(dst, file)
if err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "File uploaded: %s", handler.Filename)
}
func main() {
http.HandleFunc("/upload", uploadHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Best Practices
โ Good Practices
// Set appropriate status codes
w.WriteHeader(http.StatusOK)
// Set Content-Type header
w.Header().Set("Content-Type", "application/json")
// Handle errors properly
if err != nil {
http.Error(w, "Error message", http.StatusInternalServerError)
return
}
// Use middleware for cross-cutting concerns
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Pre-processing
next.ServeHTTP(w, r)
// Post-processing
})
}
// Validate input
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
โ Anti-Patterns
// Don't ignore errors
body, _ := io.ReadAll(r.Body)
// Don't forget to set status code
fmt.Fprintf(w, "Error") // Returns 200 OK!
// Don't mix concerns
// Keep handlers focused on single responsibility
// Don't hardcode values
// Use configuration for ports, timeouts, etc.
Resources
- Go net/http Package Documentation
- Writing Web Applications
- HTTP Status Codes
- REST API Best Practices
Summary
Building HTTP handlers in Go is straightforward with the standard library:
- Use
http.HandleFunc()for simple handlers - Implement
http.Handlerinterface for complex handlers - Use middleware for cross-cutting concerns
- Always set appropriate status codes and headers
- Validate and sanitize input
- Handle errors gracefully
With these patterns, you can build robust and maintainable web services in Go.
Comments