19 Erstellung von Web-APIs

Eine Web-API ermöglicht es, Daten und Funktionen über das HTTP-Protokoll verfügbar zu machen. RESTful-APIs (Representational State Transfer) sind eine weit verbreitete Methode zur Implementierung von Web-APIs.

19.0.1 Einfache HTTP-API mit net/http

Beginnen wir mit der Erstellung einer einfachen HTTP-API, die grundlegende CRUD-Operationen (Create, Read, Update, Delete) unterstützt:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "sync"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var (
    users  = []User{}
    nextID = 1
    mu     sync.Mutex
)

func getUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var user User
    json.NewDecoder(r.Body).Decode(&user)

    mu.Lock()
    user.ID = nextID
    nextID++
    users = append(users, user)
    mu.Unlock()

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

func getUser(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(r.URL.Query().Get("id"))

    mu.Lock()
    defer mu.Unlock()
    for _, user := range users {
        if user.ID == id {
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(user)
            return
        }
    }

    http.NotFound(w, r)
}

func updateUser(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(r.URL.Query().Get("id"))
    var updatedUser User
    json.NewDecoder(r.Body).Decode(&updatedUser)

    mu.Lock()
    defer mu.Unlock()
    for i, user := range users {
        if user.ID == id {
            users[i].Name = updatedUser.Name
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(users[i])
            return
        }
    }

    http.NotFound(w, r)
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(r.URL.Query().Get("id"))

    mu.Lock()
    defer mu.Unlock()
    for i, user := range users {
        if user.ID == id {
            users = append(users[:i], users[i+1:]...)
            w.WriteHeader(http.StatusNoContent)
            return
        }
    }

    http.NotFound(w, r)
}

func main() {
    http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodGet {
            getUsers(w, r)
        } else if r.Method == http.MethodPost {
            createUser(w, r)
        }
    })

    http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodGet {
            getUser(w, r)
        } else if r.Method == http.MethodPut {
            updateUser(w, r)
        } else if r.Method == http.MethodDelete {
            deleteUser(w, r)
        }
    })

    fmt.Println("Server started at :8080")
    http.ListenAndServe(":8080", nil)
}

In diesem Beispiel:

  1. Wir definieren einen User-Typ mit den Feldern ID und Name.
  2. Wir erstellen in-memory Speicher für Benutzer und eine Mutex zur Synchronisation.
  3. Wir implementieren Handlermethoden für das Abrufen, Erstellen, Aktualisieren und Löschen von Benutzern.
  4. Wir registrieren die Handler für die Routen /users und /user und starten den HTTP-Server.

19.1 Erweiterte API-Funktionalität

19.1.1 Validierung und Fehlerbehandlung

Um eine robuste API zu erstellen, müssen wir Eingaben validieren und Fehler korrekt behandeln:

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

    if user.Name == "" {
        http.Error(w, "Name is required", http.StatusBadRequest)
        return
    }

    mu.Lock()
    user.ID = nextID
    nextID++
    users = append(users, user)
    mu.Unlock()

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

19.1.2 Middleware

Middleware ist eine Technik, um wiederverwendbare Funktionen wie Logging, Authentifizierung oder CORS-Handling zu implementieren:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodGet {
            getUsers(w, r)
        } else if r.Method == http.MethodPost {
            createUser(w, r)
        }
    })

    mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodGet {
            getUser(w, r)
        } else if r.Method == http.MethodPut {
            updateUser(w, r)
        } else if r.Method == http.MethodDelete {
            deleteUser(w, r)
        }
    })

    loggedMux := loggingMiddleware(mux)

    fmt.Println("Server started at :8080")
    http.ListenAndServe(":8080", loggedMux)
}

In diesem Beispiel verwenden wir eine Logging-Middleware, die alle eingehenden Anfragen protokolliert.