HTTP Server

Go menyediakan package net/http yang powerful untuk membuat HTTP server. Package ini mendukung routing, middleware, dan fitur web server lainnya.

Contoh Masalah

Bagaimana cara:

  1. Membuat HTTP server
  2. Menangani routes
  3. Mengimplementasi middleware
  4. Mengelola request dan response

Penyelesaian

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

// 1. Basic data structures
type User struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Email    string `json:"email"`
}

type UserStore struct {
    sync.RWMutex
    users map[int]User
}

func NewUserStore() *UserStore {
    return &UserStore{
        users: make(map[int]User),
    }
}

// 2. 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 != "secret-token" { // Simplified auth
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// 3. Response writer wrapper
type responseWriter struct {
    http.ResponseWriter
    status int
}

func (rw *responseWriter) WriteHeader(status int) {
    rw.status = status
    rw.ResponseWriter.WriteHeader(status)
}

// 4. API handlers
type UserHandler struct {
    store *UserStore
}

func (h *UserHandler) ListUsers(w http.ResponseWriter, r *http.Request) {
    h.store.RLock()
    users := make([]User, 0, len(h.store.users))
    for _, user := range h.store.users {
        users = append(users, user)
    }
    h.store.RUnlock()

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    id := 0
    _, err := fmt.Sscanf(r.URL.Path, "/users/%d", &id)
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }

    h.store.RLock()
    user, exists := h.store.users[id]
    h.store.RUnlock()

    if !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    h.store.Lock()
    user.ID = len(h.store.users) + 1
    h.store.users[user.ID] = user
    h.store.Unlock()

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
    id := 0
    _, err := fmt.Sscanf(r.URL.Path, "/users/%d", &id)
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }

    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    h.store.Lock()
    if _, exists := h.store.users[id]; !exists {
        h.store.Unlock()
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }

    user.ID = id
    h.store.users[id] = user
    h.store.Unlock()

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
    id := 0
    _, err := fmt.Sscanf(r.URL.Path, "/users/%d", &id)
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }

    h.store.Lock()
    if _, exists := h.store.users[id]; !exists {
        h.store.Unlock()
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }

    delete(h.store.users, id)
    h.store.Unlock()

    w.WriteHeader(http.StatusNoContent)
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch {
    case r.Method == http.MethodGet && r.URL.Path == "/users":
        h.ListUsers(w, r)
    case r.Method == http.MethodGet && len(r.URL.Path) > 7:
        h.GetUser(w, r)
    case r.Method == http.MethodPost && r.URL.Path == "/users":
        h.CreateUser(w, r)
    case r.Method == http.MethodPut && len(r.URL.Path) > 7:
        h.UpdateUser(w, r)
    case r.Method == http.MethodDelete && len(r.URL.Path) > 7:
        h.DeleteUser(w, r)
    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

// 5. Static file server
func staticFileServer(dir string) http.Handler {
    return http.StripPrefix("/static/",
        http.FileServer(http.Dir(dir)))
}

func main() {
    // Initialize user store
    userStore := NewUserStore()
    userHandler := &UserHandler{store: userStore}

    // Create router
    mux := http.NewServeMux()

    // API routes
    apiHandler := authMiddleware(userHandler)
    mux.Handle("/users", apiHandler)
    mux.Handle("/users/", apiHandler)

    // Static files
    mux.Handle("/static/", staticFileServer("./static"))

    // Health check
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        json.NewEncoder(w).Encode(map[string]string{
            "status": "healthy",
        })
    })

    // Wrap with logging middleware
    handler := loggingMiddleware(mux)

    // Start server
    server := &http.Server{
        Addr:         ":8080",
        Handler:      handler,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    log.Printf("Server starting on :8080")
    log.Fatal(server.ListenAndServe())
}

Contoh Penggunaan

# Health check
curl http://localhost:8080/health

# List users
curl -H "Authorization: secret-token" http://localhost:8080/users

# Create user
curl -X POST -H "Authorization: secret-token" \
     -H "Content-Type: application/json" \
     -d '{"username":"john","email":"john@example.com"}' \
     http://localhost:8080/users

# Get user
curl -H "Authorization: secret-token" http://localhost:8080/users/1

# Update user
curl -X PUT -H "Authorization: secret-token" \
     -H "Content-Type: application/json" \
     -d '{"username":"john_updated","email":"john@example.com"}' \
     http://localhost:8080/users/1

# Delete user
curl -X DELETE -H "Authorization: secret-token" \
     http://localhost:8080/users/1

Penjelasan Kode

  1. Basic Server

    • HTTP handlers
    • Router/mux
    • Request/response
  2. Middleware

    • Logging
    • Authentication
    • Chain middleware
  3. Features

    • REST API
    • Static files
    • JSON encoding

Output

2024/01/21 11:57:22 Server starting on :8080
2024/01/21 11:57:23 GET /health 1.234ms
2024/01/21 11:57:24 GET /users 2.345ms
2024/01/21 11:57:25 POST /users 1.456ms

Tips

  • Gunakan middleware untuk cross-cutting concerns
  • Implementasi proper error handling
  • Set timeout yang sesuai
  • Gunakan HTTPS di production
  • Validasi input dengan baik