Podstawy http w Go
W zestawie standardowym Go znajduje się pakiet http, który daje możliwość
łatwego pisania serwerów i klientów HTTP.
Prosty klient
Aby napisać program pobierający stronę czy plik udostępniony po HTTP, wystarczy kilka linii kodu, z czego większość to interpretacja parametrów wywołania i obsługa ewentualnych błędów.
package main import ( "http" //od wersji 1 będzie to "net/http" "flag" "fmt" "io/ioutil" "os" ) //przetwarzanie argumentów i przełączników z linii poleceń func setup() (url, fname string) { //oczekujemy przełącznika -url var urlopt = flag.String("url", "", "Resource URL") //oczekujemy nazwy pliku do którego zapiszemy pobrany zasób w opcji -o var fnameopt = flag.String("o", "out", "Output filename") flag.Parse() //przetwarzanie fname = *fnameopt var urlarg = flag.Arg(0) //url może być argumentem a nie opcją //url z argumentu jest ważniejszy niż z przełącznika if len(*urlarg) > 0 { url = *urlarg } else if len(urlopt) > 0 { url = urlopt } else { fmt.Println("No URL provided") os.Exit(1) } return //zwracamy wartości w domyślnych zmiennych } //program właściwy func main() { url, fname := setup() //wykonujemy zapytanie resp, err := http.Get(url) if err != nil { fmt.Println(err.String()) os.Exit(1) } //czytanie odpowiedzi rbody := resp.Body buf, rerr := ioutil.ReadAll(rbody) rbody.Close() if rerr != nil { fmt.Println("Cannot read response body") fmt.Println(rerr.String()) os.Exit(1) } //zapis odpowiedzi do pliku ioutil.WriteFile(fname, buf, 0666) }
Po skompilowaniu:
6g main.go 6l main.6
możemy ściągnąć dowolny zasób z internetu np. tak:
6.out -o index.html http://golang.org.pl
W powyższym kodzie skorzystaliśmy z:
http.Get(url string) *http.Response, err os.Error, która wykonuje całą brudną robotę: nawiązuje połączenie, wysyła zapytanie i pobiera zasób.http.Responseimplementuje interfaceio.ReadCloser, co umożliwiło skorzystanie z kolejnej metody, boio.ReadCloserzłożenie interfacesówio.Readeriio.Closerio/ioutil.ReadAll(r io.Reader) []byte, err *os.Error, która ułatwia pobieranie danych z buforaio/ioutil.WriteFile(fname string, buf []byte, perm uint32), który zapisuje sekwencje bajtów do pliku o podanej nazwie/ścieżce i prawach.
zobacz także: Typ interface Interfejsy
Prosty server HTTP
Napisanie bardzo prostego serwera HTTP, też nie wymaga większego wysiłku. Poniżej znajduje się kod serwera, który obsługuje dwa zasoby "/" i "/hello":
package main import ( "http" //od wersji 1 będzie to "net/http" ) func rootHandler(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`<html> <head><title>Prosty serwer</title></head> <body> <h1>Witaj!</h1> <p> To prymitywna strona startowa bardzo prostego serwera HTTP<p> <p> Obsługuje jeszcze jeden <a href="/hello">zasób</a> </p> <body> </html>`)) } func helloHandler(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`<html> <head><title>Hello</title></head> <body> <h1>Witaj!</h1> <p> Strona prezentuje zasób "hello"</p> <body> </html>`)) } func main() { http.HandleFunc("/", rootHandler) http.HandleFunc("/hello", helloHandler) http.ListenAndServe(":8081", nil) }
W funkcji main zarejestrowaliśmy dwie funkcje typu type HandlerFunc func(ResponseWriter, *Request),
do obsługi konkretnych ścieżek. Następnie wystartowaliśmy serwer na porcie
8081. Po skompilowaniu i uruchomieniu programu możesz zobaczyć efekt za pomocą
dowolnego klienta http (np. tego, który jest powyżej).
Powyższy mechanizm pozwala na implementację praktycznie dowolnej usługi HTTP.
Warto wiedzieć, że każde wywołanie funkcji typu http.HandleFunc odbywa się w
osobnej goroutine, więc przynajmniej w teorii wydajność serwera powinna rosnąć
niemal liniowo, wraz z ilością dostępnych rdzeni procesora, ilości wątków
możliwych do uruchomienia na każdym z nich i ilością pamięci.
Nie próbowałem potwierdzać tej teorii, nie mniej moje testy wykazały, że taki serwer jest w stanie udźwignąć 16K żądań na sekundę bez większych problemów - na maszynie czterordzeniowej zużycie procesora było na poziomie 183% (czyli tak jakby 1.83 rdzenia).
Bardzo prosty serwer plikowy
Pakiet http udostępnia kilka innych umilaczy dla serwerowców. Jedną z nich
jest handler umożliwiający pisanie prostych serwerów plikowych. Najprostszy
serwer plikowy ma za zadanie serwować pliki z jakiegoś drzewa katalogów. Możemy
taki napisać w kilku linijkach:
package main import ( "http" //od wersji 1 będzie to "net/http" ) func main() { handler := http.Handle("/", http.FileServer(http.Dir("sciezka/do/katalogu/"))) http.ListenAndServe(":8081", nil) }
Tak przy okazji, zwróć uwagę na to, że do funkcji ListenAndServe przekazaliśmy
nil w drugim parametrze. Zostanie to zinterpretowane jako chęć skorzystania z
domyślnego dyspozytora zapytań. Jest to wartość typu http.ServeMux
tworzona automatycznie w pakiecie http i dostępna za pomocą zmiennej pakietowej
http.DefaultServeMux. Czym są "muxy", jak samodzielnie zarządzać połączeniem
i wiele innych kwestii poruszę (kiedyś) w osobnym artykule dla zaawansowanych.
Niecierpliwym zaś polecam przestudiowanie kodu źródłowego pliku server.go pakietu
http (nota bene ten pakiet będzie się nazywał net/http w stabilnej wersji Go 1.0)
Nie mniej funkcja http.ListenAndServe w drugim parametrze może przyjąć dowolną
wartość spełniającą interface http.Handler, czyli m.in. typ wartości zwracanej
z funkcji http.FileServer. Stąd wniosek, że powyższy kod można było by skrócić
o pierwsze wywołanie i funkcję main zdefiniować następująco:
func main() { http.ListenAndServe(":8081", http.FileServer(http.Dir("sciezka/do/katalogu/"))) }
Choć nie zawsze to jest to czego tak naprawdę chcemy. Częściej chcemy, by nasz
serwer plikowy działał jako jeden z zasobów. Czyli by pliki z katalogu
filesRootDir były dostępne np. pod zasobem "/static/". Ale FileServer
interpretuje całe URI jako ścieżkę relatywną do filesRootDir co skutkuje tym,
że jeśli nie mamy w katalogu filesRootDir katalogu static to FileServer
będzie zwracał 404.
Na szczęście dodano funkcję, która opakowuje oryginalny Handler w taki sposób,
że przed wywołaniem handlera właściwego, wycina zbędny przedrostek z URI. Jej
zastosowanie możesz prześledzić w poniższym przykładzie:
func main() { resourcePath := "static/" //tworzymy handler fileServHanler := http.FileServer(http.Dir("sciezka/do/katalogu/")) //opakowujemy go w wycinarkę przedrostków strippedHandler := http.StripPrefix(resourcePath, fileServHanler) //zarejestrujmy handler w muxie http.Handle(resourcePath, strippedHandler) //odpalamy serwer http.ListenAndServe(":8081", nil) }
Wszystko o języku programowania Go