Go dla początkujących - Część 14: Obsługa plików
Mateusz Kędziora

Hej! Oto kolejny artykuł z serii “Go dla początkujących”! Dziś zajmiemy się obsługą plików, co jest niezwykle ważnym aspektem każdego języka programowania. Nauczymy się, jak odczytywać i zapisywać pliki, obsługiwać przesyłanie i pobieranie plików przez HTTP oraz jak serwować statyczne zasoby, takie jak CSS, JavaScript i obrazy.
Ten artykuł skupia się na praktycznym zastosowaniu wiedzy. Przygotujcie się na mnóstwo kodu i konkretnych przykładów!
Wprowadzenie do Obsługi Plików w Go
Go oferuje rozbudowany pakiet os
do pracy z systemem operacyjnym, w tym z plikami. Poniżej przedstawiamy podstawowe operacje, które będziemy wykorzystywać:
- Otwieranie pliku: Funkcja
os.Open()
otwiera plik do odczytu. - Tworzenie pliku: Funkcja
os.Create()
tworzy nowy plik. - Zamykanie pliku: Funkcja
file.Close()
zamyka otwarty plik. - Czytanie z pliku: Funkcja
file.Read()
czyta dane z pliku do bufora. - Zapisywanie do pliku: Funkcja
file.Write()
zapisuje dane z bufora do pliku.
Zanim zagłębimy się w obsługę przesyłania i pobierania plików, zobaczmy prosty przykład odczytu zawartości pliku.
Odczyt Pliku w Go
Oto przykład kodu, który odczytuje zawartość pliku o nazwie example.txt
i wyświetla ją na ekranie:
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
// Spróbuj odczytać cały plik do zmiennej 'content'
content, err := ioutil.ReadFile("example.txt")
// Sprawdź, czy wystąpił błąd podczas odczytu pliku
if err != nil {
log.Fatal(err) // Wyświetl błąd i zakończ program, jeśli wystąpił problem
}
// Wyświetl zawartość pliku jako string
fmt.Println(string(content))
}
Wyjaśnienie kodu:
- Import pakietów: Importujemy pakiety
fmt
(do wyświetlania tekstu),io/ioutil
(do prostego odczytu zawartości pliku) ilog
(do obsługi błędów). ioutil.ReadFile("example.txt")
: Ta funkcja próbuje odczytać całą zawartość plikuexample.txt
i zwraca ją jako tablicę bajtów ([]byte
). Zwraca również potencjalny błąd.if err != nil { ... }
: Sprawdzamy, czy podczas odczytu pliku wystąpił błąd. Jeśli tak, to wypisujemy błąd za pomocąlog.Fatal(err)
, co spowoduje również zakończenie działania programu.fmt.Println(string(content))
: Konwertujemy tablicę bajtów (content
) na string i wyświetlamy ją na ekranie.
Jak to uruchomić:
- Stwórz plik o nazwie
example.txt
w tym samym katalogu, w którym znajduje się Twój kod Go. Wpisz do niego dowolny tekst. - Zapisz powyższy kod jako plik
main.go
. - Uruchom program za pomocą komendy
go run main.go
.
Powinieneś zobaczyć zawartość pliku example.txt
wypisaną w konsoli.
Zapis Pliku w Go
Teraz zobaczmy, jak zapisać dane do pliku. Użyjemy funkcji ioutil.WriteFile()
:
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
// Dane do zapisania w pliku
data := []byte("To jest tekst, który zostanie zapisany w pliku.")
// Nazwa pliku
filename := "output.txt"
// Zapis danych do pliku
err := ioutil.WriteFile(filename, data, 0644) // 0644 to uprawnienia pliku (rw-r--r--)
// Sprawdzenie, czy wystąpił błąd
if err != nil {
log.Fatal(err) // Jeśli wystąpił błąd, wypisz go i zakończ program
}
// Informacja o sukcesie
fmt.Printf("Zapisano dane do pliku: %s\n", filename)
}
Wyjaśnienie kodu:
data := []byte("...")
: Definiujemy tablicę bajtówdata
, która zawiera tekst do zapisania w pliku. Konwersja string na[]byte
jest konieczna, ponieważioutil.WriteFile
oczekuje danych w formie bajtów.filename := "output.txt"
: Określamy nazwę pliku, do którego chcemy zapisać dane.ioutil.WriteFile(filename, data, 0644)
: Ta funkcja zapisuje zawartość tablicy bajtówdata
do pliku o nazwiefilename
. Trzeci argument,0644
, określa uprawnienia pliku.0644
oznacza:- 6 dla właściciela pliku (odczyt i zapis)
- 4 dla grupy użytkowników (tylko odczyt)
- 4 dla pozostałych użytkowników (tylko odczyt)
if err != nil { ... }
: Sprawdzamy, czy podczas zapisu wystąpił błąd. Jeśli tak, to wypisujemy błąd i kończymy program.fmt.Printf("...")
: Wypisujemy informację o sukcesie zapisu danych do pliku.
Jak to uruchomić:
- Zapisz powyższy kod jako plik
main.go
. - Uruchom program za pomocą komendy
go run main.go
.
Po uruchomieniu programu, w tym samym katalogu powinien pojawić się plik o nazwie output.txt
z zapisaną zawartością.
Obsługa Przesyłania Plików (Upload)
Teraz przejdźmy do bardziej zaawansowanego tematu: obsługi przesyłania plików przez HTTP. Zbudujemy prosty serwer, który przyjmuje plik przesłany przez formularz HTML.
1. Kod HTML Formularza (index.html):
<!DOCTYPE html>
<html>
<head>
<title>Przesyłanie Plików</title>
</head>
<body>
<h1>Prześlij Plik</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Prześlij">
</form>
</body>
</html>
Wyjaśnienie:
action="/upload"
: Określa adres URL, na który formularz ma wysłać dane.method="post"
: Używa metody POST do przesyłania danych (metoda GET ma ograniczenia co do rozmiaru danych).enctype="multipart/form-data"
: Kluczowe! Ten atrybut informuje przeglądarkę, że formularz zawiera pliki i że dane mają być zakodowane w specjalny sposób, który umożliwia ich przesłanie.<input type="file" name="file">
: Tworzy pole do wyboru pliku. Atrybutname="file"
jest ważny, ponieważ nazwa ta będzie używana przez serwer do identyfikacji przesłanego pliku.<input type="submit" value="Prześlij">
: Przycisk do wysłania formularza.
2. Kod Serwera Go (main.go):
package main
import (
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
)
// Funkcja obsługująca wyświetlanie formularza
func indexHandler(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.Execute(w, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// Funkcja obsługująca przesyłanie pliku
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// Ograniczenie rozmiaru przesyłanego pliku (np. 10MB)
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Pobranie pliku z formularza
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "Błąd podczas pobierania pliku", http.StatusBadRequest)
return
}
defer file.Close()
// Wyświetlenie informacji o pliku
fmt.Fprintf(w, "Nazwa pliku: %s\n", handler.Filename)
fmt.Fprintf(w, "Rozmiar pliku: %d\n", handler.Size)
fmt.Fprintf(w, "Typ MIME: %s\n", handler.Header.Get("Content-Type"))
// Utworzenie pliku na serwerze
dst, err := os.Create("./uploads/" + handler.Filename)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer dst.Close()
// Kopiowanie zawartości przesłanego pliku do nowego pliku
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Plik został zapisany na serwerze jako: %s\n", handler.Filename)
}
func main() {
// Utworzenie katalogu 'uploads', jeśli nie istnieje
if _, err := os.Stat("./uploads"); os.IsNotExist(err) {
err := os.Mkdir("./uploads", 0755) // 0755 to uprawnienia (rwxr-xr-x)
if err != nil {
log.Fatal(err)
}
}
// Obsługa routingu
http.HandleFunc("/", indexHandler) // Obsługa strony głównej (formularz)
http.HandleFunc("/upload", uploadHandler) // Obsługa przesyłania pliku
// Uruchomienie serwera na porcie 8080
fmt.Println("Serwer uruchomiony na porcie 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Wyjaśnienie kodu:
import
: Importujemy potrzebne pakiety:fmt
,html/template
,io
,log
,net/http
ios
.indexHandler
: Ta funkcja obsługuje żądania do strony głównej (/
). Odpowiada za wyświetlenie formularza HTML (plikindex.html
).template.ParseFiles("index.html")
: Parsuje plik HTML i tworzy szablon.tmpl.Execute(w, nil)
: Wykonuje szablon, wysyłając wynikowy HTML do przeglądarki.
uploadHandler
: Ta funkcja obsługuje żądania POST na adres/upload
. Odpowiada za odbiór i zapisywanie przesłanego pliku.r.ParseMultipartForm(10 << 20)
: Parsuje formularz, obsługując przesyłanie plików.10 << 20
oznacza 10 MB (ograniczenie rozmiaru przesyłanego pliku).r.FormFile("file")
: Pobiera informacje o przesłanym pliku. “file” to nazwa pola w formularzu HTML (<input type="file" name="file">
).file.Close()
: Zamyka przesłany plik po użyciu.defer
zapewnia, że plik zostanie zamknięty, nawet jeśli wystąpi błąd.os.Create("./uploads/" + handler.Filename)
: Tworzy nowy plik na serwerze w kataloguuploads
. Nazwa pliku jest pobierana z przesłanego formularza (handler.Filename
).dst.Close()
: Zamyka nowo utworzony plik po użyciu.defer
zapewnia, że plik zostanie zamknięty, nawet jeśli wystąpi błąd.io.Copy(dst, file)
: Kopiuje zawartość przesłanego pliku (file
) do nowego pliku (dst
).- Funkcja wypisuje też informacje o pliku (nazwa, rozmiar, typ MIME) w odpowiedzi HTTP.
main
:os.Mkdir("./uploads", 0755)
: Tworzy kataloguploads
, jeśli jeszcze nie istnieje. Uprawnienia0755
oznaczają, że właściciel ma prawo do odczytu, zapisu i wykonywania, a grupa i inni użytkownicy mają prawo do odczytu i wykonywania.http.HandleFunc("/", indexHandler)
: Rejestruje funkcjęindexHandler
do obsługi żądań do strony głównej (/
).http.HandleFunc("/upload", uploadHandler)
: Rejestruje funkcjęuploadHandler
do obsługi żądań do adresu/upload
.http.ListenAndServe(":8080", nil)
: Uruchamia serwer HTTP na porcie 8080.
Jak to uruchomić:
- Stwórz plik
index.html
z powyższą zawartością. - Zapisz kod serwera Go jako
main.go
. - Uruchom serwer za pomocą komendy
go run main.go
. - Otwórz przeglądarkę i przejdź na adres
http://localhost:8080
. - Wybierz plik i prześlij go.
Przesłany plik powinien zostać zapisany w katalogu uploads
w tym samym katalogu, w którym uruchomiłeś serwer. Dodatkowo, w przeglądarce powinieneś zobaczyć informacje o przesłanym pliku.
Obsługa Pobierania Plików (Download)
Teraz zajmijmy się pobieraniem plików z serwera.
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
)
// Funkcja obsługująca pobieranie pliku
func downloadHandler(w http.ResponseWriter, r *http.Request) {
// Pobranie nazwy pliku z URL (np. /download/example.txt)
filename := filepath.Base(r.URL.Path)
// Ścieżka do pliku
filepath := filepath.Join("./uploads", filename)
// Sprawdzenie, czy plik istnieje
if _, err := os.Stat(filepath); os.IsNotExist(err) {
http.Error(w, "Plik nie istnieje", http.StatusNotFound)
return
}
// Ustawienie nagłówków HTTP, aby przeglądarka wiedziała, że to plik do pobrania
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
w.Header().Set("Content-Type", "application/octet-stream") // Typ MIME dla plików binarnych
// Otwarcie pliku
file, err := os.Open(filepath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
// Kopiowanie zawartości pliku do odpowiedzi HTTP
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
// Obsługa routingu
http.HandleFunc("/download/", downloadHandler) // Obsługa pobierania plików
// Uruchomienie serwera na porcie 8080
fmt.Println("Serwer uruchomiony na porcie 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Wyjaśnienie kodu:
downloadHandler
: Ta funkcja obsługuje żądania pobierania plików.filepath.Base(r.URL.Path)
: Pobiera nazwę pliku z adresu URL. Na przykład, jeśli URL to/download/example.txt
, to funkcja zwróci “example.txt”.filepath.Join("./uploads", filename)
: Tworzy pełną ścieżkę do pliku w kataloguuploads
.os.Stat(filepath)
: Sprawdza, czy plik istnieje.w.Header().Set("Content-Disposition", "attachment; filename="+filename)
: Ustawia nagłówek HTTP, który informuje przeglądarkę, że to plik do pobrania.attachment
oznacza, że przeglądarka ma wyświetlić okno dialogowe zapisu pliku.w.Header().Set("Content-Type", "application/octet-stream")
: Ustawia typ MIME naapplication/octet-stream
, co oznacza, że to plik binarny.io.Copy(w, file)
: Kopiuje zawartość pliku do odpowiedzi HTTP, co powoduje pobranie pliku przez przeglądarkę.
main
:http.HandleFunc("/download/", downloadHandler)
: Rejestruje funkcjędownloadHandler
do obsługi żądań do adresu/download/
. Zauważ, że używamy"/download/"
a nie"/download"
.http.HandleFunc
z dopiskiem/
sprawia, że funkcja zostanie wywołana dla wszystkich adresów zaczynających się od/download/
, np./download/plik.txt
,/download/folder/inny_plik.jpg
.
Jak to uruchomić:
- Zapisz kod serwera Go jako
main.go
. - Uruchom serwer za pomocą komendy
go run main.go
. - Umieść pliki, które chcesz udostępnić do pobrania, w katalogu
uploads
. - Otwórz przeglądarkę i przejdź na adres
http://localhost:8080/download/nazwa_pliku.txt
(zastąpnazwa_pliku.txt
nazwą pliku, który chcesz pobrać).
Przeglądarka powinna wyświetlić okno dialogowe zapisu pliku.
Serwowanie Statycznych Zasobów (CSS, JavaScript, Obrazy)
Bardzo często będziesz chciał serwować statyczne zasoby, takie jak pliki CSS, JavaScript i obrazy, z serwera Go. Go oferuje do tego prosty sposób.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// Obsługa statycznych zasobów z katalogu "static"
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Obsługa strony głównej (opcjonalnie)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Witaj na stronie głównej!")
})
// Uruchomienie serwera na porcie 8080
fmt.Println("Serwer uruchomiony na porcie 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Wyjaśnienie kodu:
fs := http.FileServer(http.Dir("static"))
: Tworzy handler HTTP, który serwuje pliki z katalogu “static”.http.Dir("static")
tworzy interfejsFileSystem
reprezentujący katalogstatic
.http.Handle("/static/", http.StripPrefix("/static/", fs))
: Rejestruje handlerfs
do obsługi żądań do adresu/static/
.http.StripPrefix("/static/", fs)
usuwa przedrostek “/static/” z adresu URL przed przekazaniem go dofs
. Oznacza to, że jeśli zażądasz pliku/static/style.css
, tofs
otrzyma żądanie dla plikustyle.css
w katalogustatic
.http.HandleFunc("/", ...)
: (Opcjonalnie) Obsługuje stronę główną.http.ListenAndServe(":8080", nil)
: Uruchamia serwer HTTP na porcie 8080.
Jak to uruchomić:
- Stwórz katalog o nazwie
static
w tym samym katalogu, w którym znajduje się Twój kod Go. - Umieść w katalogu
static
pliki CSS, JavaScript, obrazy, itp. - Zapisz kod serwera Go jako
main.go
. - Uruchom serwer za pomocą komendy
go run main.go
. - Otwórz przeglądarkę i przejdź na adres
http://localhost:8080/static/nazwa_pliku.css
(zastąpnazwa_pliku.css
nazwą pliku CSS, który chcesz wyświetlić) lubhttp://localhost:8080/static/obraz.jpg
.
Przeglądarka powinna wyświetlić zawartość pliku CSS lub obrazu.
Przykład użycia w HTML:
Jeśli masz plik HTML, możesz odwołać się do plików CSS i JavaScript w katalogu static
w następujący sposób:
<!DOCTYPE html>
<html>
<head>
<title>Statyczne Zasoby</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>Witaj!</h1>
<img src="/static/obraz.jpg" alt="Obraz">
<script src="/static/script.js"></script>
</body>
</html>
Podsumowanie
W tym artykule omówiliśmy podstawowe operacje związane z obsługą plików w Go: odczyt, zapis, przesyłanie, pobieranie i serwowanie statycznych zasobów. Mamy nadzieję, że ten przewodnik okazał się pomocny w zrozumieniu tych ważnych koncepcji.
Praca Domowa
- Rozbuduj serwer przesyłania plików: Dodaj walidację, która sprawdza typ przesyłanego pliku (np. czy to obraz, a nie plik wykonywalny). Wyświetl odpowiedni komunikat błędu, jeśli typ pliku jest nieprawidłowy.
- Dodaj listę plików do pobrania: Po przesłaniu pliku, dodaj go do listy dostępnej na stronie HTML. Lista powinna zawierać linki do pobrania każdego pliku.
Dodatkowe Materiały
- Oficjalna dokumentacja pakietu
os
: https://pkg.go.dev/os - Oficjalna dokumentacja pakietu
io/ioutil
: https://pkg.go.dev/io/ioutil - Oficjalna dokumentacja pakietu
net/http
: https://pkg.go.dev/net/http - A Tour of Go: https://go.dev/tour/welcome/1
Zachęcam do dalszego eksperymentowania z kodem i zapoznania się z pozostałymi artykułami z naszej serii “Go dla początkujących”! Powodzenia!
Polecane artykuły
Docker vs Kubernetes: Który dla Ciebie w 2025?
Docker i Kubernetes objaśnione! Która technologia lepsza dla początkujących w 2025? Porównanie, przykłady i przyszłość.
Mateusz Kędziora
DevOps: Automatyzacja zadań sysadmina dla programistów
Zautomatyzuj pracę sysadmina w środowisku DevOps! Praktyczne przykłady, skrypty, Ansible, Terraform, Prometheus i Grafana.
Mateusz Kędziora
Automatyzacja Linux/macOS z Bash: Praktyczny Przewodnik
Zacznij automatyzować system Linux/macOS z Bash! Dowiedz się, czym jest Bash, jak pisać skrypty i używać podstawowych komend.
Mateusz Kędziora