Ten wpis poświęcony będzie protokołowi HTTP — czyli temu, jak przeglądarka komunikuje się z serwerem.Podstawy protokołu poruszaliśmy już w lekcji 9, omawiając adnotacje Spring MVC. Dzisiaj powiemy sobie o nim znacznie więcej, dowiesz się także, co się dzieje po wpisaniu w przeglądarkę adresu www.kobietydokodu.pl ;)
Czym jest protokół HTTP
HTTP to skrót od Hypertext Transfer Protocol i jest to główny protokół używany współcześnie w przegladarkach. Jest to protokół bezstanowy, tzn. ani serwer (ani klient) nie przechowuje informacji o tym, jakie były wcześniej zapytania pomiędzy określonym serwerem i klientem oraz nie posiada stanu wewnętrznego. Powoduje to, że każde zapytanie do serwera traktowane jest jako ‘nowe’, z punktu widzenia serwera aplikacji niemożliwe do powiązania z informacjami np. o zalogowanym użytkowniku. Tą bezstanowość można obejść, obecnie głównie za pomocą tzw. ciasteczek (będzie o nich nieco dalej), należy jednak pamiętać, że HTTP sam w sobie jest bezstanowy.
Zapytania HTTP
W zapytaniach HTTP możemy wyróżnić dwa elementy: nagłówek i ciało. Zajmijmy się najpierw pierwszą częścią czyli nagłówkiem. Nagłówek ma minimum 1 wiersz, który określa metodę HTTP (poniżej omówimy sobie czym są metody, oraz do czego służy każda z nich), adres URI oraz wersję protokołu HTTP (obecnie korzystamy z wersji 1.1, oznaczanej w nagłówku jako HTTP/1.1 ). Przykładowy pierwszy wiersz nagłówka wygląda nastepująco:
GET / HTTP/1.1
Gdzie:
- GET — to nazwa metody HTTP
- / — to tzw. URI, czyli ta część adresu, który wystepuje po domenie
- HTTP/1.1 — okreslenie protokołu, tutaj HTTP w wersji 1.1
Bezpośrednio pod tą linijką możemy (ale nie musimy) podać nagłówki HTTP, każdy w nowej linii. Uwaga! Nie może być tutaj żadnej linii przerwy! Wszystko, co znajdzie się po pierwszej pustej linii (przerwie) traktowane jest jako ciało zapytania.
Znim przejdziemy do ciała, wyjaśnijmy sobie kilka konceptów.
Metoda HTTP
Metody w protokole HTTP służą do rozgraniczania różnych czynności, które mamy zamiar wykonać, pomagają także w projektowaniu przeglądarek internetowych i obsłudze zapytań. Początkowo o protokole HTTP myślano jako o protokole do obsługi plików na zdalnym serwerze — taki protokół pozwalał na pobranie zasobu, usunięcie go, wysłanie na serwer, jego aktualizację oraz pobranie metadanych. Takie znaczenie nadaje się też metodom HTTP w przypadku API spełniającego założenia REST. Tyle z teorii i historii. W praktyce metody pozwalają nam ‘rozdzielać’ zapytania trafiające pod ten sam adres — np. metody GET używamy do wyświetlenia formularza, a metody POST do jego przesłania — obie rzeczy możemy realizować pod tym samym adresem url, np. www.kobietydokodu.pl/jakis/formularz . Poniżej znajdziesz podsumowanie metod protokołu HTTP:
Metoda | Request body | Response body | Zastosowanie / opis |
---|---|---|---|
GET | niedozwolone | opcjonalnie | Pobieranie zasobu lub jego wyświetlenie, np. wyświetlenie formularza lub strony. Parametry można przekazywac jedynie poprzez adres (np. ?nazwa=wartosc&nazwa2=wartosc2) |
POST | opcjonalne | opcjonalnie | Przesłanie danych zapisanych jako pary klucz-wartość do serwera (np. wysłanie formularza, gdzie kluczem jest nazwa danego pola a wartością wpisana przez nas wartość). Metoda ta pozwala przesyłać także pliki (a także wiele pliki oraz pary klucz-wartość jednocześnie). Parametry są przekazywane w ciele zapytania, można także przekazywać parametry poprzez adres (tak jak w metodzie GET) |
PUT | opcjonalne | opcjonalnie | Przesyłanie ‘paczki’ danych, np. jednego pliku. Metoda ta ma pewne ograniczenia, np. nie ma możliwości łaczenia par klucz-wartość z inną przesyłaną treścią (np. plikiem). Obecnie używana głównie w przypadku RESTowych serwisów, gdzie ciałem jest np. formularz zapisany w postaci JSONa. |
DELETE | opcjonalnie | opcjonalnie | Usuwanie zasobu na serwerze, z racji bezpieczeństwa praktycznie zawsze jest wyłaczona domyślnie. Obecnie używana głównie w przypadku RESTowych serwisów, wskazując, że dany zasób ma być usunięty (i obsługiwany przez aplikację, a nie sam serwer). |
HEAD | niedozwolone | niedozwolone | Analogiczny do zapytania GET, z tym wyjątkiem, że nie zwraca ciała (zawartości). Służy do pobrania metdanych o zasobie w postaci nagłówków HTTP. Dla danego adresu zwraca same nagłówki. |
Istnieją jeszcze metody OPTIONS, TRACE oraz CONNECT, ale nie mają one dużego zastosowania z punktu widzenia programisty aplikacji webowych.
Pełną specyfikację możesz znaleźć na stronie http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html, jest to formalna definicja metod HTTP.
Nagłówki HTTP
Nagłówki HTTP spotkamy zarówno w zapytaniach, jak i w odpowiedziach. Są one pierwszymy liniami, oddzielone od ciała jedną pustą linią. Nagłówki są opcjonalne — protokół nie wymaga ich obecności. Nagłówki to pewnego rodzaju metadane i polecenia wymieniane przez przeglądarkę i serwer — mogą się w nich znaleźć informacje takie jak rodzaj przesyłanych treści (np. czy jest to obrazek czy plik JSON), sugestia dotycząca traktowania zawartości (czy przeglądarka ma wyświetlić daną treść, czy np. potraktować to jako pobieranie), jaki jest rozmiar przesyłanych danych, kiedy były modyfikowane, jakiego rodzaju odpowiedzi druga strona się spodziewa itp.
Nagłówki przyjmują postać klucz-wartość, zapisywane w postaci:
Klucz: wartość
Najpierw jest klucz (przeważnie zaczyna się dużą literą, ale nie jest to wymagane), następnie dwukropek, spacja oraz wartość.
W przeważającej większości nagłówki są automatycznie ustawiane przez serwer i aplikacja nie musi ich modyfikować / uzupełniać. Są jednak sytuacje, w których możemy chcieć wysłać określony nagłówek, mamy wtedy taką możliwość.
Pełną listę standardowych nagłówków możesz znaleźć np. na Wikipedii, my omówimy sobie tylko te najważniejsze. Co ważne — nagłówek może mieć dowolną nazwę (klucz), nie musi ona być spośród tych określonych standardami. Dzięki temu możliwe jest implementowanie dodatkowej funkcjonalności pomiędzy serwerami, które się ze sobą komunikują. Możemy też dołaczać dowolne nagłówki — jeśli odbiorca wiadomości nie wie, jak je zinterpretować, po prostu je zignoruje.
Poniższa tabela podsumowuje najczęściej używane nagłówki
Nagłówek | Opis | Przykład |
---|---|---|
Content-Type | W zapytaniu oraz odpowiedzi określa, jakiego typu dane są przesyłane | Content-Type: application/json |
Content-Length | W zapytaniu oraz odpowiedzi zawiera informacje ile danych jest przesyłanych | Content-Length: 20 |
Cookie | W zapytaniu przesyła zawartość Cookies przechowywanych dla danej witryny. Może przechowywać wiele wartości w postaci klucz=wartość, pary oddzielane są od siebie średnikami. | Cookie: AcceptedCookiePolicy=1; Country=Poland; |
Set-Cookie | W odpowiedzi jest to polecenie serwera, aby przeglądarka ustawiła wartości Cookie; podobnie jak nagłówek Cookie może zawierać wiele par postaci klucz=wartość oddzielonych średnikami | Set-Cookie: UserID=JanNowak; SeenTutorial=1 |
Location | W odpowiedzi instuuje przeglądarkę o tym, że ma wykonać zapytanie pod inny adres. W ten sposób (w połaczeniu ze statusem np. 302) w aplikacji możemy przekierowywać pod inny adres | Location: http://calieminnaaplikacja.com.pl/nowawersja |
Last-Modified | W odpowiedzi serwer może poinformować, kiedy nastąpiła ostatnia zmiana zawartości. Format daty jest specyficzny dla protokołu HTTP i określony w dokumencie RCF 7231 | Last-Modified: Tue, 15 May 2015 12:45:26 GMT |
Content-Disposition | W odpowiedzi serwer może poinstuować przeglądarkę, aby zamiast wyświelać treść, pobrała ją. Można też określić nazwę, pod jaką przeglądarka powinna zasugerować zapisanie pliku | Content-Disposition: attachment; filename=“raport_roczny.pdf” |
Host | W zapytaniu jest to nagłówek obowiązkowy, informuje serwer pod jaki adres domeny chcemy wysłać zapytanie (może to być też adres IP). Pomaga to serwerom obsługującym wiele domen prawidłowo przekierowywać zapytania | Host: www.kobietydokodu.pl |
Accept | W zapytaniu klient może poinformować serwer, jakiego typu odpowiedzi akceptuje. Dzięki temu serwer może zadecydować o wysłaniu odpowiedzi np. w XML a nie JSON, co ma zastosowanie w wielu API | Accept: application/xml |
Statusy HTTP (kody odpowiedzi)
Jednym z elementów protokołu HTTP są kody odpowiedzi, zwanymi też statusami. To numeryczne, trzycyfrowe kody, które są dołączane do odpowiedzi i sygnalizują status odpowiedzi. Najpopularniejsze i najczęściej spotykane kody to 200 (OK, czyli wszystko jest w porządku, zapytanie jest obsłużone), 302 (przekierowanie), 403 (brak dostępu) oraz 404 (nie znaleziono — stąd liczba 404 często pojawia się na stronach z informacją, że wpisany adres nie istnieje).
Kodów jest mnóstwo, najczęściej wystarczy jednak znajomość tych podstawowych. Co ważne, nawet nie znając kodu można określić jego przybliżone znaczenie na podstawie numeru. Kody są bowiem podzielone na grupy, pierwsza cyfra kodu mówi nam, z której jest on grupy. I tak:
- 1xx — informacyjne, nieczęsto można spotkać, dotyczą bardziej środowiska niż samej aplikacji (np. 111 — serwer odrzucił połaczenie)
- 2xx — zapytanie się powiodło
- 3xx — przekierowanie, zapytanie należy kierować pod inny adres / serwer
- 4xx — błąd aplikacji spowodowany działaniem użytkownika (np. wspomniany 404 — nie znaleziono — czy 403 — brak dostępu lub 400 — niepoprawnie zapytanie)
- 5xx — błąd serwera (np. nieobsłużony wyjątek w Javie)
Listę kodów można znaleźć m.in. na Wikipedii. Są one także opisane i zdefiniowane w dokumencie RFC 2616 z późniejszymi zmianami.
W ramach ciekawostki polecam poczytać o kodzie odpowiedzi 418, który jest częścią wielu implementacji, znalazł się także w Spring Framework.
Ciało zapytania (ang. request body)
Ciało zapytania to nic innego jak dane, które wysyłamy do serwera. Dobrą praktyką jest to, aby nie dołączać ciała zapytania dla metod GET oraz DELETE. Wynika to z faktu, że teoretycznie zapytania te powinny być pozbawione jakichkolwiek dodatkowych informacji dla serwera, choć standard tego wprost nie zabrania. Problem może pojawić się po stronie użytkowników lub bibliotek — wiele serwerów proxy (wykorzystywanych np. w firmach) i bibliotek (używanych przy pisaniu klientów i aplikacji korzystających z Twojego API) zakłada, że tego ciała nie ma, co może powodować problemy jeśli Twoja aplikacja go jednak oczekuje. Z tego powodu lepiej używać go tylko w przypadku zapytań POST / PUT.
Ciało metody może zawierać właściwie cokolwiek — jest to po prostu strumień danych, ale ponownie istnieje wiele konwencji. Najważniejsza (i jednocześnie ustandauryzowana) to wykorzystanie nagłówka Content-Type aby ‘powiedzieć’ serwerowi w jakim formacie chcemy wysłać dane i jak należy je interpretować. Sam format jak i dostępne opcje są określone w standardzie RFC6838 i pokrewnych (mniej techniczny opis z przykładami można także znaleźć na stronie https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types, a aktualną listę na stronie https://www.iana.org/assignments/media-types/media-types.xhtml). My skupimy się na kilku najczęściej wykorzystywanych we współczesnych aplikacjach.
application/json
Szczególnie popularny w ostatnich latach wraz z rozwojem aplikacji frontendowych oraz standaryzacją tego obszaru. JSON to skrót od JavaScript Object Notation i jest to nic innego jak reprezentacja obiektów w sposób zgodny z językiem JavaScript. Zastosowania wykraczają jednak znacznie poza język JavaScript — główne przyczyny to fakt, że taki sposób zapisu danych łatwo monitorować w sposób automatyczny, jest to zapis zrozumiały dla człowieka (a więc programista może w łatwy sposób testować i weryfikować swoje aplikacje), jest on względnie ustandaryzowany, jest raczej zwięzły (w porównaniu do np. XMLa) i bardzo dobrze się kompresuje z użyciem algorytmów używanych jako standardowe w sieci web (gzip itp). Przykładowe ciało zapytania może wyglądać tak:
{"strona": "kobietydokodu.pl", "ocena":"świetna"}
application/x‑www-form-urlencoded
Ten rodzaj treści oznacza, że ciało metody zawiera dane (najczęściej przesłane z formularza) zapisane tak, jakbyśmy przesyłali je w adresie URL. To domyślny sposób wysyłania formularzy na stronach WWW, obecnie spotykany głównie w przypadku aplikacji czysto backendowych lub integracji aplikacji frontendowych ze starszymi serwisami. Przykładowa treść ciała zapytania może wyglądać nastepująco:
strona=kobietydokodu.pl&ocena=rewelacyjna
Powiązanym rodzajem jest multipart/form-data — wykorzystywany głównie do przesyłania plików w przypadku tradycyjnych formularzy WWW. Składnia ciała jest jednak bardziej skomplikowana i z tego powodu właściwie nie implementuje się jej wprost (ewentualnie w połączeniu z istniejącymi bibliotekami). Pozwala ona na przesłanie kilku różnych elementów o różnych typach w ramach jednego zapytania (stąd najczęściej jest wykorzystywana do przesyłania plików ze strony WWW na serwer).
Bezstanowość HTTP a ciasteczka
Jak wspominaliśmy, protokół HTTP jest bezstanowy, tzn nie ‘przechowuje’ informacji o tym, co działo się wcześniej. Jest to oczywisty problem w większości przypadków, kiedy korzystamy z narzędzi wymagających zalogowania się — informacja o tym, jaki użytkownik jest zalogowany musi być przechowywana w jakiś sposób.
Rozwiązaniem stosowanym obecnie na szeroka skalę są tzw. ciasteczka — pierwotnie mające postać plików tekstowych w formacie klucz=wartość, obecnie przechowywane w wewnętrznej bazie danych przeglądarki.
Formalnie ciasteczka (ang. cookies) to zbiór par klucz-wartość przypisanych do danej domeny (w ogólnym przypadku; możliwe jest też utworzenie cookies dla ścieżki, np. kobietydokodu.pl/jednaaplikacja) które są wysyłane do serwera z każdym zapytaniem. Oczywiście ze względów bezpieczeństwa nie przechowuje się w nich danych użytkownika, do tego służą tzw. sesje, czyli kolekcje danych przechowywane po stronie serwera. W ciasteczkach najczęściej zapisuje się tzw. klucz sesji — unikalny identyfikator, na podstawie którego serwer ma możliwość powiązania jednego ze zbiorów danych które przechowuje z wysyłającym zapytanie klientem. Najczęściej w zależności od technologii klucz ten ma określoną nazwę — w przypadku Javy standard JavaEE definiuje go jako JSESSIONID (w Servlet API 3, które jest częścią standardu Java EE 6 można zmienić to w pliku web.xml).
Oczywiście większość współczesnych serwerów ma dodatkowe zabezpieczenia, sprawdza np. adres IP klienta powiązanego z sesją, żeby upewnić się że nie ma miejsca próba przechwycenia sesji (tzw. atak typu Session Hijacking). Ogólnie jednak ciasteczka i jedna zapisana w nich wartość jest podstawą wystarczającą do wprowadzenia stanowości w aplikacjach webowych.
Jeśli chodzi o inne informacje przechowywane w ciasteczkach to tutaj jest mnóstwo możliwości — od śledzenia zachowań użytkownika (np. Google Analytics przechowuje w ciasteczku infomacje, żeby powiązać kilka zachowań danego klienta z tym samym użytkownikiem w ich systemie) poprzez przechowywanie informacji o preferencjach (np. kolejność sortowania listy elementów aby następnym razem od razu ją uporządkować w ten sposób) po zachowania jednorazowe (np. czy użytkownik zaakceptował politykę cookies albo ‘ukrył’ jakąś część interfejsu).
To, o czym trzeba jeszcze pamiętać, to fakt, że ciasteczka zwykle ‘żyją’ dłużej niż sesja — w teorii informacje tam zapisane mogą być wieczne (w praktyce, żyją do kolejnego ‘wyczyszczenia’ pamięci podręcznej przeglądarki przez użytkownika). Z tego powodu jest to dobre miejsce do przechowywania informacji jawnych, niezwiązanych z danymi które nasz system przetwarza, np. preferencji dot. wyglądu interfejsu użytkownika, wszelkich informacji pomagających nam zwiększyć komfort użytkownika w korzystaniu z naszego systemu.
Z punktu widzenia bezpieczeństwa, informacje w ciasteczkach są przesyłane jawnie w protokole HTTP, bez szyfrowania i można nimi stosunkowo łatwo manipulować po stronie klienta. Dlatego należy bezwzględnie unikać przechowywania tam informacji niejawnych (przede wszystkim loginów/haseł, ale też danych jak adres email, jakiekolwiek dane, do których dostęp w systemie wymaga autoryzacji) a także po stronie aplikacji traktować wszystkie informacje z ciasteczek jako niezaufane (konieczna jest każdorazowa walidacja i weryfikacja, jeśli z nich korzystamy bezpośrednio). Z tego powodu poza sesją i śledzeniem zachowań użytkowników, ciasteczka mają znacznie szersze zastosowanie w technologiach frontendowych (zapisywanie preferencji użytkownika dot. układu strony, wyświetlania elementów itp), gdzie służą wygodzie i nieirytowania użytkownika powtarzalnymi pytaniami za każdym razem.
Bonus: co się dzieję, kiedy wpiszesz w przeglądarce adres www.kobietydokodu.pl ?
To pytanie ma drugie dno — poza tym, że jest pewnego rodzaju podsumowaniem powyższych informacji, pada ono bardzo często na rozmowach rekrutacyjnych :) Zacznijmy więc od początku.
Pierwsze co się wydarzy to przeglądarka spróbuje przetłumaczyć adres url (www.kobietydokodu.pl) na adres IP (np. 188.128.171.252) . Aby to zrobić, najpierw sprawdzana jest pamęć podręczna. Tam przeglądarka (lub system operacyjny) przechowuje m.in. ostatnie wyniki takiego tłumaczenia. Jeśli nie znajdzie go w pamięci podręcznej, wysyła zapytanie do serwera DNS. Serwery DNS służą właśnie do tego, aby tłumaczyć nazwy domen na adresy IP. Po otrzymaniu odpowiedzi przeglądarka zna już adres IP (czyli adres serwera w sieci) pod jaki ma wysłać zapytania.
Drugim krokiem jest wysłanie zapytania do serwera działającego pod wskazanym adresem. Zapytanie to wysyłane jest na port 80 (jest to domyślny port protokołu HTTP) i jest to zapytanie typu GET. Serwer przetwarza zapytanie i jeśli wszystko jest w porządku odpowiada treścią (np. stroną HTML) oraz statusem 200 — OK.
Jeśli chodzi o to, co dzieje się po stronie serwera, to zależy od użytej technologii. My przeanalizujemy oczywiście serwer aplikacji Java EE.
Wstępem jest zamiana zapytania HTTP na obiekt typu HttpServletRequest. Dopiero wtedy ma miejsce właściwe przetwarzanie.
Na początku serwer aplikacji analizuje adres URI — bierze jego pierwszy segment (do pierwszego /) weryfikując, czy istnieje aplikacja uruchomiona pod takim adresem. Jeśli tak, ta część jest usuwana z adresu URI, a zapytanie jest przekazywane do tej aplikacji, jeśli nie to zapytanie jest kierowane do aplikacji o ścieżce / (jeśli oczywiście istnieje). Następnie serwer aplikacji na podstawie informacji z pliku XML kieruje zapytanie do odpowiedniego obiektu, który implementuje interfejs Servlet. Tutaj już przychodzi czas na naszą aplikację — Servlety to obiekty, których implementacja jest częścią aplikacji.
W przypadku aplikacji napisanych z użyciem Spring’a, wszystkie zapytania (najczęściej) trafiają do jednego obiektu typu DispatcherServlet, który na podstawie adnotacji nad kontrolerami, plików konfiguracji oraz plików XML wywołuje metody odpowiednich klas.
To trochę uproszczony obraz, w grę wchodzą jeszcze filtry, intrceptory, potencjalnie mogą też aspekty, jednak nie zmieniają one idei i dokładniejszym ich omówieniem zajmiemy się przy innej okazji.
Podsumowanie
Dzisiaj omówiliśmy jak działa protokół HTTP, z rzeczy, które musisz wiedzieć, to:
- jest bezstanowy, oparty o architekture klient-serwer i działający w trybie zapytani-odpowiedź
- co to są nagłówki i gdzie się je przekazuje
- jak wygląda zapytanie
- co to są kody odpowiedzi (statusy) i jakie wyróżniamy grupy
- co to są ciasteczka i do czego służą
Ta wiedza wystarczy do tego, aby zrozumieć większość materiałów związanych z samym protokołem HTTP, na stanowisku Junior developera także powinna być wystarczająca. Zachęcam jednak do dalszego zgłębiania i doczytania o prawdopodobnie najbardziej zasłużonym protokole na świecie.