Go dla początkujących - Część 9: Pierwszy serwer HTTP

2/7/2025 Kurs Go

Mateusz Kędziora

image

Hej! Witaj w kolejnej - 9 już części kursu go. W tym artykule pokaże Ci, jak stworzyć prosty serwer HTTP, który może obsługiwać żądania i zwracać odpowiedzi. Go, dzięki swojej prostocie, wydajności i wbudowanemu pakietowi net/http, jest doskonałym wyborem dla web developmentu, zwłaszcza dla początkujących.

Zacznijmy od podstaw.

Czym jest Serwer HTTP?

Serwer HTTP to program komputerowy, który nasłuchuje na określonym porcie (zazwyczaj 80 dla HTTP i 443 dla HTTPS) i odpowiada na żądania HTTP od klientów (np. przeglądarek internetowych). Klient wysyła żądanie (ang. request) do serwera, a serwer przetwarza to żądanie i wysyła odpowiedź (ang. response) z powrotem do klienta. Odpowiedź zazwyczaj zawiera dane, takie jak strona HTML, obrazek, plik JSON, itp.

net/http – Podstawa Web Developmentu w Go

Pakiet net/http w Go dostarcza podstawowe funkcje potrzebne do budowy serwerów HTTP i klientów HTTP. Umożliwia tworzenie serwerów, obsługę żądań, ustawianie nagłówków HTTP, przesyłanie danych i wiele więcej.

Krok 1: Tworzymy Prosty Serwer

Oto najprostszy przykład serwera HTTP w Go:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Witaj, świecie!")
	})

	fmt.Println("Serwer uruchomiony na porcie 8080")
	http.ListenAndServe(":8080", nil)
}

Omówienie Kodu:

  1. package main: Definiuje pakiet main, co oznacza, że to jest program wykonywalny.
  2. import (...): Importuje pakiety fmt (do formatowania tekstu i wyświetlania na konsoli) i net/http (serwer http).
  3. http.HandleFunc("/", ...): To kluczowa funkcja. Rejestruje funkcję obsługi żądań dla ścieżki ”/”. Oznacza to, że gdy ktoś wyśle żądanie HTTP do naszego serwera na adres główny (np. http://localhost:8080/), zostanie wywołana ta funkcja.
    • Pierwszym argumentem jest ścieżka URL, którą chcemy obsługiwać.
    • Drugi argument to funkcja anonimowa (lub funkcja typu http.HandlerFunc), która obsługuje żądanie.
  4. func(w http.ResponseWriter, r *http.Request): To funkcja obsługująca żądanie.
    • w http.ResponseWriter: Interfejs, który pozwala nam wysłać odpowiedź HTTP do klienta.
    • r *http.Request: Wskaźnik do struktury Request, która zawiera informacje o żądaniu HTTP, takie jak adres URL, nagłówki, metoda HTTP (GET, POST, PUT, DELETE, itp.) i dane przesłane w żądaniu.
  5. fmt.Fprintf(w, "Witaj, świecie!"): Zapisuje tekst “Witaj, świecie!” do http.ResponseWriter (w). To jest treść odpowiedzi HTTP, którą zobaczy klient w swojej przeglądarce.
  6. fmt.Println("Serwer uruchomiony na porcie 8080"): Wyświetla wiadomość na konsoli informującą o tym, że serwer został uruchomiony.
  7. http.ListenAndServe(":8080", nil): Uruchamia serwer HTTP na określonym porcie.
    • Pierwszy argument to adres, na którym serwer będzie nasłuchiwał (“:8080” oznacza wszystkie interfejsy sieciowe na porcie 8080).
    • Drugi argument to http.Handler, który obsługuje wszystkie przychodzące żądania. W tym przypadku ustawiliśmy go na nil, co oznacza, że używany jest domyślny handler, który deleguje żądania do zarejestrowanych funkcji obsługi (takich jak ta zarejestrowana za pomocą http.HandleFunc).

Jak uruchomić serwer:

  1. Zapisz powyższy kod w pliku o nazwie main.go.
  2. Otwórz terminal/konsolę i przejdź do katalogu, w którym zapisałeś plik.
  3. Uruchom serwer, wpisując go run main.go.

Powinieneś zobaczyć komunikat “Serwer uruchomiony na porcie 8080” w konsoli.

Jak przetestować serwer:

  1. Otwórz przeglądarkę internetową.
  2. Wpisz adres http://localhost:8080/ w pasku adresu i naciśnij Enter.

Powinieneś zobaczyć tekst “Witaj, świecie!” wyświetlony w przeglądarce.

Krok 2: Obsługa Routingu

Routing to proces kierowania żądań HTTP do odpowiednich funkcji obsługi na podstawie adresu URL. Załóżmy, że chcemy obsługiwać różne adresy URL, takie jak / (strona główna), /o-nas i /kontakt. Możemy to zrobić za pomocą kilku http.HandleFunc.

package main

import (
	"fmt"
	"net/http"
)

func homePage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Strona główna!")
}

func aboutUsPage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "O nas!")
}

func contactPage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Kontakt!")
}

func main() {
	http.HandleFunc("/", homePage)
	http.HandleFunc("/o-nas", aboutUsPage)
	http.HandleFunc("/kontakt", contactPage)

	fmt.Println("Serwer uruchomiony na porcie 8080")
	http.ListenAndServe(":8080", nil)
}

Omówienie Kodu:

  1. homePage, aboutUsPage, contactPage: Definiujemy trzy funkcje obsługi żądań, każda dla innej strony.
  2. http.HandleFunc("/", homePage): Rejestruje funkcję homePage dla adresu ”/”.
  3. http.HandleFunc("/o-nas", aboutUsPage): Rejestruje funkcję aboutUsPage dla adresu “/o-nas”.
  4. http.HandleFunc("/kontakt", contactPage): Rejestruje funkcję contactPage dla adresu “/kontakt”.

Jak przetestować routing:

  1. Uruchom serwer, wpisując go run main.go w terminalu.
  2. Otwórz przeglądarkę i odwiedź następujące adresy:
    • http://localhost:8080/ (powinieneś zobaczyć “Strona główna!”)
    • http://localhost:8080/o-nas (powinieneś zobaczyć “O nas!”)
    • http://localhost:8080/kontakt (powinieneś zobaczyć “Kontakt!”)

Krok 3: Obsługa Parametrów URL

Często chcemy przekazywać dane do serwera poprzez parametry URL. Na przykład, możemy chcieć wyświetlić profil użytkownika o określonym ID, np. http://localhost:8080/uzytkownik?id=123. Go udostępnia mechanizmy do łatwego wyciągania tych parametrów.

package main

import (
	"fmt"
	"net/http"
	"strconv"
)

func userProfile(w http.ResponseWriter, r *http.Request) {
	queryParams := r.URL.Query()
	idStr := queryParams.Get("id")

	if idStr == "" {
		fmt.Fprintf(w, "Brak parametru 'id'")
		return
	}

	id, err := strconv.Atoi(idStr)
	if err != nil {
		fmt.Fprintf(w, "Nieprawidłowy format parametru 'id'")
		return
	}

	fmt.Fprintf(w, "Profil użytkownika o ID: %d", id)
}

func main() {
	http.HandleFunc("/uzytkownik", userProfile)

	fmt.Println("Serwer uruchomiony na porcie 8080")
	http.ListenAndServe(":8080", nil)
}

Omówienie Kodu:

  1. r.URL.Query(): Wyciąga parametry URL z żądania HTTP (r). Zwraca mapę (typ url.Values), w której klucze to nazwy parametrów, a wartości to slice stringów (ponieważ parametr może mieć wiele wartości).
  2. queryParams.Get("id"): Pobiera wartość parametru “id” z mapy queryParams. Jeśli parametr nie istnieje, zwraca pusty string.
  3. if idStr == "": Sprawdza, czy parametr “id” został przekazany. Jeśli nie, wysyła odpowiedź z informacją o braku parametru.
  4. strconv.Atoi(idStr): Konwertuje string idStr na liczbę całkowitą. Funkcja Atoi zwraca liczbę całkowitą i błąd.
  5. if err != nil: Sprawdza, czy wystąpił błąd podczas konwersji stringu na liczbę. Jeśli tak, wysyła odpowiedź z informacją o nieprawidłowym formacie parametru.
  6. fmt.Fprintf(w, "Profil użytkownika o ID: %d", id): Wyświetla komunikat z ID użytkownika, który został pobrany z parametru URL.

Jak przetestować parametry URL:

  1. Uruchom serwer, wpisując go run main.go w terminalu.
  2. Otwórz przeglądarkę i odwiedź następujące adresy:
    • http://localhost:8080/uzytkownik?id=123 (powinieneś zobaczyć “Profil użytkownika o ID: 123”)
    • http://localhost:8080/uzytkownik (powinieneś zobaczyć “Brak parametru ‘id’”)
    • http://localhost:8080/uzytkownik?id=abc (powinieneś zobaczyć “Nieprawidłowy format parametru ‘id’“)

Krok 4: Obsługa Metod HTTP (GET, POST)

Różne metody HTTP (GET, POST, PUT, DELETE, itp.) służą do wykonywania różnych operacji na serwerze. Najczęściej używane to:

  • GET: Pobieranie danych z serwera.
  • POST: Wysyłanie danych do serwera w celu utworzenia nowego zasobu.

W tym przykładzie pokażemy, jak obsługiwać metody GET i POST.

package main

import (
	"fmt"
	"net/http"
)

func handleForm(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		// Wyświetl formularz HTML
		fmt.Fprintf(w, `
			<form method="POST">
				Imię: <input type="text" name="imie"><br>
				<input type="submit" value="Wyślij">
			</form>
		`)
	case http.MethodPost:
		// Przetwórz dane z formularza
		imie := r.FormValue("imie")
		fmt.Fprintf(w, "Witaj, %s!", imie)
	default:
		http.Error(w, "Nieobsługiwana metoda", http.StatusMethodNotAllowed)
	}
}

func main() {
	http.HandleFunc("/formularz", handleForm)

	fmt.Println("Serwer uruchomiony na porcie 8080")
	http.ListenAndServe(":8080", nil)
}

Omówienie Kodu:

  1. r.Method: Zawiera metodę HTTP użytą w żądaniu (GET, POST, PUT, DELETE, itp.).
  2. switch r.Method: Używamy instrukcji switch do rozróżnienia obsługi dla różnych metod HTTP.
  3. http.MethodGet: Stała reprezentująca metodę GET.
  4. http.MethodPost: Stała reprezentująca metodę POST.
  5. fmt.Fprintf(w, ...) (dla GET): Wysyła formularz HTML do przeglądarki. Formularz zawiera pole tekstowe “imie” i przycisk “Wyślij”.
  6. r.FormValue("imie"): Pobiera wartość pola “imie” z danych formularza przesłanych metodą POST.
  7. fmt.Fprintf(w, "Witaj, %s!", imie) (dla POST): Wyświetla powitanie z imieniem, które zostało przesłane w formularzu.
  8. http.Error(w, "Nieobsługiwana metoda", http.StatusMethodNotAllowed): Wysyła odpowiedź HTTP z kodem błędu 405 (Method Not Allowed) w przypadku, gdy metoda HTTP jest nieobsługiwana.

Jak przetestować metody HTTP:

  1. Uruchom serwer, wpisując go run main.go w terminalu.
  2. Otwórz przeglądarkę i odwiedź adres http://localhost:8080/formularz.
  3. Powinieneś zobaczyć formularz. Wpisz swoje imię w polu tekstowym i naciśnij “Wyślij”.
  4. Po wysłaniu formularza powinieneś zobaczyć powitanie z Twoim imieniem.

Podsumowanie i Praca Domowa

Gratulacje! Utworzyłeś swój pierwszy serwer HTTP w Go, nauczyłeś się obsługiwać routing, parametry URL i różne metody HTTP. To dopiero początek Twojej przygody z web developmentem w Go.

Praca Domowa:

  1. Rozszerz kod serwera o obsługę metody PUT i DELETE dla np. zasobów “produktów”.
  2. Dodaj obsługę statycznych plików (np. CSS, JavaScript, obrazki) za pomocą funkcji http.FileServer.
  3. Spróbuj użyć zewnętrznego routera, takiego jak Gorilla Mux, aby uprościć obsługę routingu.

Przydatne Linki:

Pamiętaj, najlepszym sposobem na naukę jest praktyka. Eksperymentuj z kodem, modyfikuj go, dodawaj nowe funkcje i czytaj dokumentację. Nie bój się popełniać błędów – to najlepszy sposób na naukę!

Zachęcam również do zapoznania się z innymi moimi artykułami na temat Go, które pomogą Ci rozwinąć Twoje umiejętności programistyczne. Znajdziesz tam informacje na temat struktur danych, concurrency, testowania i wielu innych aspektów języka Go.

Powodzenia w dalszej nauce!

Polecane artykuły