Go dla początkujących - Część 11: Routing Gorilla Mux

2/9/2025 Kurs Go

Mateusz Kędziora

image

Jeśli dopiero zaczynasz swoją przygodę z Go, to trafiłeś idealnie. W tym artykule skupimy się na jednym z kluczowych aspektów tworzenia aplikacji internetowych: routingu. Routing to proces, który pozwala skierować przychodzące żądania HTTP do odpowiednich funkcji (tzw. handlerów), które obsłużą to żądanie.

Wyobraź sobie, że piszesz sklep internetowy. Gdy użytkownik wejdzie na adres /produkty, chcesz wyświetlić listę produktów. Gdy wejdzie na /produkty/123, chcesz wyświetlić szczegóły produktu o ID 123. Routing pozwala ci zdefiniować, która funkcja w twoim kodzie odpowiada za obsługę każdego z tych adresów.

W Go można to zrobić na kilka sposobów. Zaczniemy od prostego przykładu z użyciem wbudowanej biblioteki net/http, a następnie przejdziemy do bardziej zaawansowanej i popularnej biblioteki Gorilla Mux.

Podstawowy routing z net/http

Go posiada wbudowaną bibliotekę net/http, która oferuje podstawowe narzędzia do obsługi żądań HTTP. Oto jak można zdefiniować prosty routing:

package main

import (
	"fmt"
	"net/http"
)

// Handler dla strony głównej
func homeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Witaj na stronie głównej!")
}

// Handler dla strony o produktach
func productsHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Lista produktów!")
}

func main() {
	// Rejestrujemy handlery dla konkretnych ścieżek
	http.HandleFunc("/", homeHandler)       // Obsługuje żądania na adres "/"
	http.HandleFunc("/produkty", productsHandler) // Obsługuje żądania na adres "/produkty"

	// Uruchamiamy serwer na porcie 8080
	fmt.Println("Serwer uruchomiony na porcie 8080")
	http.ListenAndServe(":8080", nil) // Drugi argument to handler, nil oznacza użycie defaultServeMux
}

Wyjaśnienie kodu:

  • package main: Definiuje pakiet główny.
  • import: Importuje pakiety fmt (do formatowania tekstu) i net/http (do obsługi HTTP).
  • homeHandler(w http.ResponseWriter, r *http.Request): To funkcja obsługująca żądania do strony głównej (/).
    • w http.ResponseWriter: Odpowiada za wysyłanie odpowiedzi do klienta (przeglądarki).
    • r *http.Request: Zawiera informacje o żądaniu, takie jak adres URL, nagłówki, etc.
    • fmt.Fprintln(w, "Witaj na stronie głównej!"): Wysyła tekst “Witaj na stronie głównej!” jako odpowiedź.
  • productsHandler(w http.ResponseWriter, r *http.Request): Funkcja obsługująca żądania do strony produktów (/produkty).
  • http.HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)): Rejestruje funkcję handler do obsługi żądań na ścieżce path.
    • "/": Ścieżka dla strony głównej.
    • "/produkty": Ścieżka dla strony z listą produktów.
  • http.ListenAndServe(addr string, handler http.Handler): Uruchamia serwer HTTP.
    • ":8080": Adres i port, na którym serwer będzie nasłuchiwał.
    • nil: Używa domyślnego routera (defaultServeMux).

Uruchom ten kod (zapisz go jako main.go i użyj go run main.go) i otwórz w przeglądarce adresy http://localhost:8080/ i http://localhost:8080/produkty. Powinieneś zobaczyć odpowiednie komunikaty.

Ograniczenia net/http:

Podstawowy routing z net/http jest prosty, ale ma kilka ograniczeń:

  • Brak obsługi parametrów URL: Trudno jest obsłużyć dynamiczne adresy URL, takie jak /produkty/123, gdzie 123 jest ID produktu. Trzeba by ręcznie parsować URL.
  • Brak wsparcia dla różnych metod HTTP: Trudno jest rozróżnić żądania GET, POST, PUT, DELETE dla tej samej ścieżki.
  • Brak wsparcia dla middleware: Trudno jest dodać funkcje, które wykonują się przed i po obsłudze każdego żądania (np. logowanie, autentykacja).
  • Prosty routing: Dla bardziej złożonych aplikacji routing staje się trudny do zarządzania.

Dlatego właśnie potrzebujemy bardziej zaawansowanej biblioteki do routingu.

Gorilla Mux: Potężny routing dla Go

Gorilla Mux to popularna biblioteka routingowa dla Go, która rozwiązuje powyższe problemy i oferuje wiele dodatkowych funkcji. Aby jej użyć, musisz ją zainstalować:

go get -u github.com/gorilla/mux

Oto przykład użycia Gorilla Mux:

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
)

// Handler dla strony głównej
func homeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Witaj na stronie głównej!")
}

// Handler dla strony o produkcie (z ID)
func productHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r) // Pobieramy parametry z URL
	productID := vars["id"]  // Odczytujemy parametr "id"

	fmt.Fprintf(w, "Szczegóły produktu o ID: %s\n", productID)
}

func main() {
	// Tworzymy nowy router
	r := mux.NewRouter()

	// Definiujemy trasy
	r.HandleFunc("/", homeHandler)
	r.HandleFunc("/produkty/{id}", productHandler) // {id} to parametr URL

	// Uruchamiamy serwer
	fmt.Println("Serwer uruchomiony na porcie 8080")
	http.ListenAndServe(":8080", r) // Podajemy nasz router jako handler
}

Wyjaśnienie kodu:

  • import "github.com/gorilla/mux": Importuje bibliotekę Gorilla Mux.
  • r := mux.NewRouter(): Tworzy nowy router Gorilla Mux.
  • r.HandleFunc("/", homeHandler): Rejestruje handler homeHandler dla ścieżki /. Działa tak samo, jak w net/http.
  • r.HandleFunc("/produkty/{id}", productHandler): Rejestruje handler productHandler dla ścieżki /produkty/{id}. {id} jest tutaj parametrem URL. Oznacza to, że wszystko, co znajdzie się w miejscu {id} w adresie URL, zostanie przechwycone jako parametr o nazwie “id”.
  • vars := mux.Vars(r): Pobiera mapę parametrów URL z żądania.
  • productID := vars["id"]: Odczytuje wartość parametru “id” z mapy vars.
  • fmt.Fprintf(w, "Szczegóły produktu o ID: %s\n", productID): Wyświetla szczegóły produktu z podanym ID.
  • http.ListenAndServe(":8080", r): Uruchamia serwer, przekazując nasz router r jako handler. To kluczowe, ponieważ chcemy, aby Gorilla Mux zarządzało routingiem.

Uruchom ten kod i spróbuj wejść na adresy http://localhost:8080/ i http://localhost:8080/produkty/123. Powinieneś zobaczyć szczegóły produktu o ID 123. Zauważ, że możesz zmienić liczbę po /produkty/ i zobaczysz, że handler automatycznie przechwytuje ten parametr.

Obsługa różnych metod HTTP

Gorilla Mux pozwala łatwo rozróżnić żądania GET, POST, PUT, DELETE i inne. Możesz to zrobić używając metody Methods():

r.HandleFunc("/produkty", createProductHandler).Methods("POST")
r.HandleFunc("/produkty/{id}", getProductHandler).Methods("GET")
r.HandleFunc("/produkty/{id}", updateProductHandler).Methods("PUT")
r.HandleFunc("/produkty/{id}", deleteProductHandler).Methods("DELETE")

W tym przykładzie:

  • createProductHandler będzie wywoływany tylko dla żądań POST na adres /produkty.
  • getProductHandler będzie wywoływany tylko dla żądań GET na adres /produkty/{id}.
  • updateProductHandler będzie wywoływany tylko dla żądań PUT na adres /produkty/{id}.
  • deleteProductHandler będzie wywoływany tylko dla żądań DELETE na adres /produkty/{id}.

To bardzo przydatne przy tworzeniu API RESTful.

Middleware w Gorilla Mux

Middleware to funkcje, które wykonują się przed i/lub po handlerze. Można ich użyć do różnych celów, takich jak:

  • Logowanie żądań
  • Uwierzytelnianie użytkowników
  • Sprawdzanie uprawnień
  • Dodawanie nagłówków HTTP

Oto jak można zdefiniować prosty middleware w Gorilla Mux:

// Middleware do logowania żądań
func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Wykonujemy kod przed handlerem
		fmt.Println("Odebrano żądanie:", r.URL.Path)

		// Wywołujemy następny handler w łańcuchu
		next.ServeHTTP(w, r)

		// Wykonujemy kod po handlerze (opcjonalne)
	})
}

Wyjaśnienie kodu:

  • loggingMiddleware(next http.Handler) http.Handler: Funkcja middleware przyjmuje http.Handler (następny handler w łańcuchu) jako argument i zwraca nowy http.Handler.
  • http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ... }): Tworzy nowy handler z anonimowej funkcji.
  • fmt.Println("Odebrano żądanie:", r.URL.Path): Loguje ścieżkę URL żądania.
  • next.ServeHTTP(w, r): Wywołuje następny handler w łańcuchu. To kluczowe, aby żądanie zostało obsłużone.

Aby użyć tego middleware, musisz go dodać do routera:

r := mux.NewRouter()
r.Use(loggingMiddleware) // Dodajemy middleware do routera

Teraz każde żądanie, które zostanie obsłużone przez ten router, zostanie najpierw “przechwycone” przez loggingMiddleware, które wypisze ścieżkę URL do konsoli.

Można łączyć wiele middleware, tworząc łańcuch. Kolejność middleware ma znaczenie, ponieważ są one wykonywane w kolejności, w jakiej zostały dodane.

Subroutery

Subroutery pozwalają na grupowanie tras pod wspólnym prefiksem. Jest to przydatne do tworzenia bardziej zorganizowanych aplikacji.

Oto przykład:

s := r.PathPrefix("/api/v1").Subrouter() // Tworzymy subrouter z prefiksem "/api/v1"
s.HandleFunc("/produkty", listProductsHandler).Methods("GET")
s.HandleFunc("/produkty/{id}", getProductHandler).Methods("GET")

W tym przykładzie:

  • r.PathPrefix("/api/v1").Subrouter(): Tworzy subrouter, który obsługuje tylko żądania, których ścieżka zaczyna się od /api/v1.
  • s.HandleFunc("/produkty", listProductsHandler).Methods("GET"): Rejestruje handler dla ścieżki /api/v1/produkty (zwróć uwagę na prefiks).
  • s.HandleFunc("/produkty/{id}", getProductHandler).Methods("GET"): Rejestruje handler dla ścieżki /api/v1/produkty/{id} (zwróć uwagę na prefiks).

Subroutery mogą również mieć własne middleware. Dzięki temu możesz dodać middleware, które będą dotyczyły tylko określonej grupy tras.

Inne funkcje Gorilla Mux

Gorilla Mux oferuje wiele innych przydatnych funkcji, takich jak:

  • Regexp Route Matching: Możesz używać wyrażeń regularnych do dopasowywania adresów URL.
  • Custom Matchers: Możesz tworzyć własne matchery do dopasowywania tras na podstawie dowolnych kryteriów.
  • URL Building: Możesz generować adresy URL na podstawie nazw tras i parametrów.

Zachęcam do zapoznania się z dokumentacją Gorilla Mux, aby dowiedzieć się więcej o tych funkcjach: https://github.com/gorilla/mux

Porównanie net/http i Gorilla Mux

Funkcjanet/httpGorilla Mux
Parametry URLBrak wbudowanej obsługiWbudowana obsługa z użyciem {parametr}
Metody HTTPOgraniczone wsparciePełne wsparcie z Methods()
MiddlewareBrak wbudowanego wsparciaWbudowane wsparcie z Use()
SubrouteryBrak wbudowanego wsparciaWbudowane wsparcie z PathPrefix().Subrouter()
Routing złożonyTrudny do zarządzaniaŁatwy do zarządzania
Złożoność projektuWystarczający dla małych projektówZalecany dla większych projektów

Podsumowując, net/http jest dobry do prostych aplikacji, ale Gorilla Mux jest zdecydowanie lepszym wyborem dla bardziej złożonych aplikacji, które wymagają parametrów URL, różnych metod HTTP, middleware i subrouterów.

Praca domowa

  1. Stwórz prosty API RESTful dla książek.

    • Użyj Gorilla Mux.
    • Zdefiniuj trasy dla:
      • GET /books - Pobranie listy wszystkich książek.
      • POST /books - Dodanie nowej książki.
      • GET /books/{id} - Pobranie książki o danym ID.
      • PUT /books/{id} - Aktualizacja książki o danym ID.
      • DELETE /books/{id} - Usunięcie książki o danym ID.
    • Użyj prostych struktur danych w pamięci (np. slice) do przechowywania książek. Nie musisz łączyć się z bazą danych.
    • Dodaj middleware do logowania każdego żądania (ścieżka URL i metoda HTTP).
  2. Dodaj uwierzytelnianie do API.

    • Dodaj middleware, który sprawdza nagłówek Authorization.
    • Jeśli nagłówek Authorization jest poprawny (np. zawiera poprawne hasło API), zezwól na dostęp do zasobów.
    • W przeciwnym razie zwróć błąd 401 Unauthorized.

To ćwiczenie pomoże Ci utrwalić wiedzę na temat routingu, parametrów URL, metod HTTP i middleware w Gorilla Mux.

Podsumowanie

W tym artykule omówiliśmy routing w Go, zaczynając od podstawowej biblioteki net/http, a następnie przechodząc do bardziej zaawansowanej biblioteki Gorilla Mux. Nauczyliśmy się definiować trasy, obsługiwać parametry URL, middleware i subroutery.

Gorilla Mux to potężne narzędzie, które znacznie ułatwia tworzenie aplikacji internetowych w Go. Zachęcam do eksperymentowania z różnymi funkcjami Gorilla Mux i do dalszego zgłębiania wiedzy na temat Go.

Przydatne linki

Pamiętaj, że nauka programowania to proces ciągłego uczenia się i eksperymentowania. Nie bój się próbować nowych rzeczy i zadawać pytania. Powodzenia! I koniecznie sprawdź inne artykuły na moim blogu, aby pogłębić swoją wiedzę o Go!

Polecane artykuły