HTTP czyli podstawowy budulec aplikacji internetowych
Protokoły sieciowe są standaryzowane przez organizację Interner Engineering Task Force (IETF) w dokumentach o nazwie Request For Comments (RFC) Obecnie obowiązujący standard dla komunikacji HTTP w wersji 1.1 to RFC 9112 w połączeniu z RFC 9110 oraz RFC 9111
Z jednej strony protokół HTTP na pierwszy rzut oka wydaje się przerażający, jeżeli zaczniemy od czytania powyższych trzech dokumentów. Jednak pierwsze specyfikacje nie były tak rozległe, a nawet w nich część rozwiązań była bardzo rzadko używana. Stąd u niektórych przekonanie, że HTTP jest prostym protokołem, ale jak twórca narzędzia curl pisze w swoim artykule pozory mylą.
Ćwiczenie 1:
- Kiedy opublikowana została najnowsza wersja protokołu HTTP/1.1?
- Kiedy opublikowana została poprzednia wersja tego protokołu?
Co w URI piszczy, czyli czym jest adres wpisany do przeglądarki?
URI czyli Unified Resource Identifier to kolejny termin zdefiniowany przez jeszcze inny dokument RFC, jednak idźmy już dalej tą ścieżką. Z punktu widzenia aplikacji internetowych URI to po prostu link, który pozwala nam wejść na interesującą nas stronę.
Próba przeczytania w całości dowolnego dokumentu RFC szybko kończy się kilkoma otwartymi zakładkami w przeglądarce i uczuciem, że zaraz zacznie boleć nas głowa. Warto takie ćwiczenie wykonać na jakimś w miarę niedużym dokumencie. Po przeczytaniu kilku z nich można nabrać pewnej wprawy i zacząć rozróżniać informacje znaczące od pomijalnych.
Przykładowy nietrywialny adres internetowy może wyglądać na przykład tak:
http://www.example.com/path/to/resource?min=10&max=20#my-favorite-paragraphDzieląc go na poszczególne elementy, mamy kolejno:
http://zwany schemą, określającą m.in. którego portu użyje przeglądarka próbując połączyć się z serwerem i jakim protokołem odezwie się do maszyny po drugiej stroniewww.example.comczyli po angielsku authority albo bardziej potocznie host, czyli nazwa bądź adres serwera, gdzie znajduje się stronapath/to/resourceczyli ścieżka wskazująca na konkretny zasób?min=10&max=20to tak zwane query pozwalające np. zawęzić kryteria wyszukiwania, albo posortować elementy na stronie w konkretny sposób#my-favorite-paragraphczyli fragment pozwalający wskazać konkretny element na stronie
Ściśle rzecz ujmując fragment nie wchodzi w skład protokołu HTTP. Po wpisaniu w przeglądarkę adresu z fragmentem przeglądarka nie przesyła go do serwera jako część żądania HTTP. Za chwilę to sprawdzimy.
Metody, czasowniki czyli czego chcemy od serwera?
O ile URI odpowiada na pytanie co? o tyle drugą niezbędną częścią żądania HTTP jest odpowiedź na pytanie jak?. Korzystając z internetu nie tylko pobieramy informacje, ale też wysyłamy nowe treści, edytujemy te istniejące albo kasujemy nieaktualne. Wszystkie te działania mają odzwierciedlenie w HTTP. Protokół definiuje tzw. metody albo potocznie po angielsku verbs. Najczęściej używane z nich to:
GETdo pobieraniaPOSTdo wysyłaniaDELETEdo usuwania
Prócz tych istnieją jeszcze PUT, PATCH, HEAD, OPTIONS oraz wiele innych, o których wielu programistów nawet nie słyszało. To nie jest przesadzone stwierdzenie.
Statusy HTTP
Serwer HTTP po otrzymaniu żądania o dany zasób powinien się do tego żądania jakoś ustosunkować. Jeżeli serwer jest w stanie spełnić żądanie, odeśle klientowi treść danego zasobu, czyli np. stronę HTML, zdjęcie, plik CSS, etc., ale poprzedzi tę informację tzw. status code, albo po prostu statusem.
Statusy to 3-cyfrowe kody, które dzielą się na 5 głównych grup, pogrupowanych po pierwszej cyfrze:
- 1xx statusy informacyjne
- 2xx statusy pozytywne
- 3xx statusy przekierowujące
- 4xx statusy błędu klienta
- 5xx statusy błędu serwera
Na pewno w swoim korzystaniu spotkaliście się z kodem 404 oznaczającym nieznalezioną stronę, to typowy przykład błędu z rodziny 4xx. W przypadku poprawnie sformułowanego żądania obsłużonego z powodzeniem serwer powinien odpowiedzieć kodem 200.
Pełną listę statusów możecie znaleźć na stronie IANA
Nagłówki HTTP
Zarówno klient kontaktujący się z serwerem jak i serwer odpowiadający na żądanie mogą chcieć dodać do swoich wiadomości dodatkowe informacje. Najczęściej są one dołączane do wiadomości jako tzw. nagłówki HTTP, które są po prostu parami w formie Nazwa: Wartość.
Lista zdefiniowanych przez standardy nagłówków jest długa. W tej lekcji na pewno spotkamy się z nagłówkiem Content-Type, który jest używany przez serwer do informowania klienta (zazwyczaj przeglądarki) o tym jak należy interpretować treść odpowiedzi, tj. czy jest to dokument HTML, zwykły tekst, czy np. dane w formacie JSON.
Pierwszy program Node.js
Korzystając ze swojego środowiska programistycznego, otwórz terminal i uruchom komendy:
mkdir first-server
cd first-server
npm init --yes
touch index.jsObjaśnienia:
- Komenda
mkdirtworzy katalog o podanej nazwie. Od make directory. - Komenda
cdzmienia obecny katalog na podany. Od change directory npm initinicjalizuje konfigurację modułunpm. Będzie nam ona potrzebna jak zaczniemy dodawać biblioteki i narzędzia. Konfiguracja znajduje się w plikupackage.json. Argument--yespowoduje, że npm przyjmuje wartości domyślne we wszystkich polach konfiguracji.- Komenda
touchtworzy nowy, pusty plik o podanej nazwie. Gdyby plik istniał, treść pliku pozostała by bez zmian, ale zmieniłaby się ostatnia data zapisu pliku.
W ten sposób zainicjalizujemy strukturę naszego pierwszego projektu Node.js. W tym samym oknie terminala możesz wpisać następującą komendę, aby otworzyć nowo utworzony folder w Code
code -r .Najpierw zaczniemy od wprowadzenia drobnej modyfikacji do naszego pliku konfiguracyjnego w package.json.
1{
2 "name": "first-server",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "type": "module",
7 "scripts": {
8 "test": "echo \"Error: no test specified\" && exit 1"
9 },
10 "author": "",
11 "license": "ISC"
12}Dodanie klucza "type": "module" pozwoli nam na importowanie modułów w plikach w sposób zgodny ze współczesnym JavaScriptem.
Tradycyjnie pierwszy program w nowym języku programowania to Hello World, więc zacznijmy od tego. Dopisz następujący kod do index.js
1console.log("Hello world!");A następnie w terminalu uruchom komendę:
node index.jsGratulacje, oto Twój pierwszy program w Node.js! Ale nie zatrzymujmy się tutaj, pójdźmy o krok dalej.
Pierwszy serwer HTTP w Node.js
Node.js jest środowiskiem uruchomieniowym dla języka JavaScript, ale oprócz tego, zapewnia także bibliotekę narzędzi pozwalających np. czytać i zapisywać informacje do plików na dysku, uruchamiać proste serwery sieciowe, czy mierzyć wydajność naszych aplikacji.
W przypadku postawienia serwera WWW zaczniemy tego kawałka kodu. Przepisz go do swojego edytora.
1import { createServer } from 'node:http';
2
3// Create a HTTP server
4const server = createServer((req, res) => {
5 res.writeHead(200, { 'Content-Type': 'text/plain' });
6 res.end('hello world!');
7});
8
9const port = 8000;
10const host = "localhost";
11
12// Start the server
13server.listen(port, host, () => {
14 console.log(`Server listening on http://${host}:${port}`);
15});Dlaczego napisałem o przepisywaniu kodu, a nie o wklejeniu go do swojego edytora? O ile doceniam ułatwianie sobie życia, o tyle na etapie uczenia się nowych rzeczy jak języków programowania czy frameworków, dobrze jest dać sobie trochę czasu na poznanie podstaw nowych technologii. W przeciwnym wypadku kiedy przyjdzie nam napisać cokolwiek bez dostępu do internetu lub innych zewnętrznych pomocy, może się okazać, że nie potrafimy się posługiwać własnymi narzędziami.
Powyższy kawałek kodu ma zaledwie kilkanaście linii, a jednak dużo się w nim wydarzyło. Przejdźmy przez niego powoli.
Importowanie zewnętrznych modułów
import { createServer } from 'node:http';Aby móc skorzystać z jakiejkolwiek funkcjonalności z poza naszego pliku z kodem, musimy zaimportować coś z innego modułu, tj. pliku. Node.js zawiera różne moduły, w tym wypadku chcemy zaimportować funkcję createServer z modułu http z kolekcji modułów z Node.js. Funkcja, jak nietrudno zgadnąć, służy do stworzenia serwera HTTP.
Taka forma importowania to tzw. nazwany import (ang. named import). Alternatywnie moglibyśmy zaimportować obiekt domyślnie eksportowany przez moduł node:http używając tzw. domyślnego importu (ang default import), wtedy wywołanie funkcji createServer wyglądałoby następująco:
import http from 'node:http';
const server = http.createServer();
// ...
Więcej informacji o importowaniu z innych modułów znajdziecie pod tym linkiem do dokumentacji języka JavaScript.
Dokumentacja, szukanie informacji i podpowiadanie składni
Powyżej znajduje się link do dokumentacji języka JavaScript. Dokumentacja języka bądź narzędzia z którego korzystamy zazwyczaj dostarczy nam najpełniejszych odpowiedzi na temat tego, jak działają nasze narzędzia. Oto krótkie podsumowanie, gdzie szukać wartościowych informacji:
- Dokumentacja, dla JavaScript i standardów internetowych to głównie serwis Mozilla Developer Network. Node.js ma własne strony z dokumentacją ale trzeba się upewnić, że korzystamy z dokumentacji dla używanej przez nas wersji. Kolejną opcją są agregatory dokumentacji takie jak np. (DevDocs)[https://devdocs.io], które bardzo polecam.
- Google z adnotacją, żeby upewnić się, że nie korzystamy ze zbyt starych źródeł i żeby nie ufać wszystkiemu co przeczytamy w internecie.
- Podpowiedzi w edytorze, jeżeli odpowiednio skonfigurowaliśmy podpowiadanie składni.
Domyślnie edytor VS Code stara się nam podpowiadać jak może, jednak czasem musimy mu lekko pomóc. Jeżeli Code nie podpowiada nic na temat funkcji createServer, wykonaj następującą komendę w swoim terminalu:
npm install --save-dev @types/nodeDefiniowanie funkcji w JavaScript
Prosty przykład definicji własnej funkcji i wywołania jej może wyglądać w JavaScript w ten sposób:
// definicja funkcji myFunction
function myFunction() {
console.log("hello!");
}
// wywołanie funkcji myFunction
myFunction();Nieco inaczej możemy zdefiniować funkcję np. w ten sposób:
// definicja anonimowej funkcji i przypisanie jej do stałej myFunction
const myFunction = function () {
console.log("hello!");
}
// wywołanie funkcji wskazywanej przez myFunction
myFunction();Są subtelne różnice w tych sposobach, ale ostatecznie wynik działania programu jest ten sam.
Obecnie dużo częściej wykorzystywaną składnią są tzw. arrow functions. Składnia ta wygląda następująco:
// definicja anonimowej funkcji i przypisanie jej do stałej myFunction
const myFunction = () => {
console.log("hello!");
}
// wywołanie funkcji wskazywanej przez myFunction
myFunction();Funkcje mogą przyjmować też argumenty, które zmieniają działanie funkcji. Aby tak się stało, musimy zdefiniować funkcję razem z parametrami, np. tak jak w poniższym przykładzie:
// definicja funkcji myFunction
function myFunction(name, age) {
console.log("hello! My name is", name, "and I am", age, "years old");
}
// wywołanie funkcji myFunction
myFunction("Douglas", 42);Używając składni arrow function, ta definicja może wyglądać w ten sposób:
// definicja funkcji myFunction
const myFunction = (name, age) => {
console.log("hello! My name is", name, "and I am", age, "years old");
}Funkcje mogą być po prostu zbiorem instrukcji, ale mogą też zwracać przetworzoną wartość. Dodawanie to może niezbyt praktyczny przykład, ale użyjemy go ze względu na prostotę:
// definicja funkcji add
function add(a, b) {
return a+b;
}
// wywołanie funkcji myFunction
console.log("2 + 3 = ", add(2, 3));Jak widać wywołanie funkcji może być też użyte jako argument innej funkcji.
Szablony ciągów znaków
Sporą częścią naszych programów będzie przetwarzanie tekstu. JavaScript pozwala na tworzenie szablonów tekstowych, ang. template literals, które pozwalają na umieszczanie wartości zmiennych z kodu w wynikowym łańcuchu znaków. Same szablony umieszczamy pomiędzy dwoma znakami backtick, czyli `, natomiast w środku możemy umieszczać znaczniki ${}, gdzie pomiędzy klamrami możemy wstawiać nazwy zmiennych bądź wywołania funkcji, których wartości pojawią się jako fragment tekstu.
Tak wygląda przykładowe zastosowanie:
const item = "sword";
const weight = "3 lb";
console.log(`You found a ${item}, it weighs ${weight}. Take it?`);
function pokedex_entry(name, elements) {
return `Pokemon name: ${name} \nElements: ${elements}`;
}
console.log(pokedex_entry("Charmander", "Fire"));Kombinacja znaków \n wstawia w ciąg znaków znak nowej linii.
Przekazywanie funkcji jako argumenty do innych funkcji
W języku JavaScript funkcje są tzw. obywatelami pierwszej kategorii. To znaczy, że są traktowane dokładnie tak samo jak wszystkie inne elementy w języku, np. zmienne i stałe. Wszędzie tam, gdzie możemy stworzyć i użyć zmiennej, tam powinniśmy móc stworzyć i użyć funkcji. To oznacza, że możemy też przekazać funkcję jako argument do innej funkcji.
Istotne rozróżnienie: przekazanie funkcji jako argumentu to nie to samo, co wywołanie funkcji i przekazanie jej zwrotu jako argumentu. Przejdźmy przez kilka przykładów:
function make_introduction_en(name, age) {
return `My name is ${name} and I am ${age} years old`;
}
function make_introduction_pl(name, age) {
return `Mam na imię ${name} i mam ${age} lata`;
}
function greet(greeting, make_introduction) {
console.log(greeting, make_introduction("Douglas", 42));
}
greet("Hello!", make_introduction_en);
greet("Cześć!", make_introduction_pl);Ok, czyli możemy w jednej funkcji wywoływać inną funkcję przekazaną jako argument. Spójrzmy na kolejny przykład, tym razem dodając mały zwrot akcji. Jeżeli chcemy przekazać funkcję jako argument, to nie musimy jej wcześniej definiować w innym miejscu. Możemy ją zdefiniować w miejscu wywołania funkcji, do której chcemy przekazać tę nową.
function transform_number_and_print(number, transfromer) {
console.log(transfromer(number));
}
function add_2(x) {
return 2 + x;
}
const multiply_by_5 = (x) => {
return x * 5;
};
transform_number_and_print(5, add_2);
transform_number_and_print(5, multiply_by_5);
transform_number_and_print(5, (x) => {
return x + 3;
});
transform_number_and_print(5, function (x) {
return add_2(multiply_by_5(x));
});Tworzenie serwera HTTP Node.js
Po tym wszystkim możemy przejść do wyjaśnienia, co się dokładnie dzieje w naszym pliku index.js
1import { createServer } from 'node:http';
2
3// Create a HTTP server
4const server = createServer((req, res) => {
5 res.writeHead(200, { 'Content-Type': 'text/plain' });
6 res.end('hello world!');
7});
8
9const port = 8000;
10const host = "localhost";
11
12// Start the server
13server.listen(port, host, () => {
14 console.log(`Server listening on http://${host}:${port}`);
15});Omówienie:
- Linia 1: Importujemy z modułu
node:httpfunkcjęcreateServer - Linie 4-7: Tworzymy obiekt serwera wywołując funkcję
createServer, jednocześnie przekazując do niej nową funkcję, która będzie obsługiwać żądania przychodzące do serwera. Każde żądanie przychodzące w tym przypadku obsłużymy dokładnie tak samo, czyli:- zwrócimy status HTTP 200, czyli damy znać klientowi, że wszystko poszło ok
- dodamy do odpowiedzi nagłówek HTTP
Content-Typeo wartościtext/plain, czyli damy znać drugiej stronie, że odsyłamy zwykły tekst - wpiszemy do odpowiedzi tekst
hello world
- Linie 9-10: Tworzymy sobie dwie stałe pomocnicze zawierające numer portu, na którym ma działać serwer oraz adres na którym ma działać.
localhostoznacza, że serwer będzie działał lokalnie - Linie 13-15: Uruchamiamy serwer wywołując metodę
listenna obiekcieserver. Do metody listen podajemy 3 argumenty: port serwera, adres oraz funkcję callback, która zostanie uruchomiona, kiedy serwer zacznie działać. W tej funkcji jedyne co robimy, to raportujemy, na którym porcie i pod jakim adresem działa serwer.
Sprawdzenie serwera HTTP
Po uruchomieniu serwera komendą:
node index.jsVS Code powinno pozwolić nam otworzyć przeglądarkę pod adresem “http://localhost:8000/”. Jeżeli przeglądarka wyświetliła tekst “hello world!”, to gratulacje! Właśnie uruchomiłeś swój własnoręcznie napisany serwer HTTP w Node.js.
Sprawdź w narzędziach deweloperskich przeglądarki, jak wygląda żądanie i odpowiedź odesłana przez serwer. Czy są tam jakieś niespodziewane rzeczy? Albo dziwne żądania?
Jeżeli masz ochotę, możesz też spróbować wysłać żądanie do serwera używając konsolowego programu curl na swojej maszynie wirtualnej:
curl --verbose http://localhost:8000Albo, jeżeli czujesz przypływ odwagi, możesz wykorzystać program telnet do własnoręcznego napisania swojego własnego żądania HTTP.
> telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.0
Host: localhost:8000
Accept: */*
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Mon, 08 Sep 2025 12:33:55 GMT
Connection: close
hello world!Connection closed by foreign host.Podsumowanie
Postawiliśmy dziś swój pierwszy serwer HTTP oraz poznaliśmy podstawy tego protokołu.
W następnych lekcjach będziemy budować na tej wiedzy i tworzyć bardziej zaawansowaną aplikację internetową.