w Praktyce
10 godz. 44 min · Node.js · Full-stack i Programowanie
Piotr PalarzWeb DeveloperW kursie tym poznasz platformę Node.js od podstaw, aż po bardziej zaawansowane koncepcje. Zaczniemy od omówienia czym jest Node, a także jakie może być zastosowanie tej technologii. Następnie przejdziemy przez proces instalacji i napisanie swojego pierwszego skryptu. Już na samym początku dokładnie omówimy tworzenie własnych modułów, gdyż jest to wiedza niezbędna, by dobrze zrozumieć funkcjonowanie Node. Następnie dowiesz się czym są zdarzenia, jak działa model “publish / subscribe”, a także czym jest “event-driven development”. Chwilę później omówimy pracę z buforami, stream’ami, a także ze standardowym wejściem i wyjściem. Ta wiedza pozwoli Ci zrozumieć jak tworzone aplikacje mogą otrzymywać od użytkownika dane oraz jak je wyświetlają.
Niezwykle ważnym rozdziałem będzie ten, poświęcony pracy z systemem plików. Szczególnie dla dewelopera, pracującego dotychczas z językiem JavaScript wyłącznie w środowisku przeglądarki internetowej. Node pozwala bowiem tworzyć nowe pliki, zapisywać w nich dane oraz odczytywać i usuwać pliki i katalogi. Przy wszystkich tych operacjach, niezwykle ważna jest jednak asynchroniczna natura Node. Dowiesz się jak działa, a także jakie są jej zalety. Na tym etapie nie zabraknie oczywiście praktycznego projektu, którym będzie aplikacja typu CLI (Command Line Interface). Pozwoli nam ona grupowo zmienić nazwy wielu plików, według podanego wzorca.
W kolejnym rozdziale poruszymy fundamentalną dla Node kwestię, mianowicie tworzenie serwerów sieciowych. Zaczniemy od najniższego poziomu, tworząc prosty serwer bazujący na protokole TCP/IP. Wiedza w jaki sposób Node radzi sobie z takim zadaniem, pozwoli później lepiej zrozumieć wyższe warstwy, takie jak np. HTTP czy WebSockets. Chwilę później stworzymy swój pierwszy serwer HTTP, do którego będzie można wysyłać zapytania wprost z przeglądarki internetowej. Omówimy również jak w Node wykorzystać bezpieczne szyfrowanie HTTPS.Na tym etapie będziesz już miał solidną wiedzę jak pracować z Node, jednak zanim przejdziemy dalej, zatrzymamy się, by omówić jak dokładnie działa Node.js, a także jak ta platforma została zbudowana. Dwie dość obszerne lekcje, będą być może jednymi z najważniejszych w tym kursie. Dowiesz się w nich czym jest, a także jak działa jednowątkowa pętla zdarzeń, czym jest proces i wątek, a także zobaczysz kod źródłowy Node.js i wszystkie bloki budulcowe, z jakich Node się składa. To naszym zdaniem niezwykle ważny temat, często jednak pomijany w wielu materiałach. Dzięki dogłębnemu zrozumieniu jednowątkowej natury języka JavaScript i asynchroniczności Node, będziesz mógł tworzyć dużo lepsze aplikacje.
Node.js posiada kilkanaście wbudowanych modułów, z których w dowolnej chwili można skorzystać. Wszystkie jednak dają nam dużą, niskopoziomową kontrolę, ale czasami sporo kodu należy napisać, by zrealizować jakieś zadanie. Jest to jednak świadomy wybór twórców Node, gdyż całą resztę świetnych rozwiązań, dopisuje nieustannie ogólnoświatowa społeczność deweloperów. Zdecydowana większość z modułów ląduje w rejestrze npm. Jest to genialny package manager, który pozwoli nam wyszukiwać, pobierać i aktualizować niezliczoną ilość modułów. Dowiesz się zatem jak korzystać z npm, a chwilę później zaczniemy wykorzystywać zgromadzone tam moduły w dalszej pracy.Jednym z najpopularniejszych modułów jest express.js, który pozwala w bardzo prosty sposób tworzyć serwery HTTP. Grzechem byłoby zatem z niego nie skorzystać. Dowiesz się wszystkiego co niezbędne, by zacząć tworzyć własne aplikacje bazujące na protokole HTTP z użyciem express.js, a także kilku dodatków do tego systemu. Zobaczysz jak routować zapytania, jak korzystać z middleware czy serwować statyczne pliki. Następnie omówimy współpracę z bazą danych MongoDB i z jej wykorzystaniem stworzymy API typu REST. Stworzona aplikacja pozwoli odczytywać dane z bazy i przesyłać je do klienta w formacie JSON, a także dodawać nowe dane, aktualizować i usuwać je. Wszystko to zgodnie z dobrymi praktykami pracy z REST.
Chwilę później czeka Cię kolejny praktyczny projekt - skracacz linków. Stworzymy wspólnie aplikację, która pozwoli skrócić dowolny adres URL do krótkiej formy. Oczywiście będzie działać również w drugą stronę! Kiedy przejdziemy pod skrócony adres, przekieruje nas do odpowiedniej witryny. Node.js to platforma wręcz stworzona do tworzenia aplikacji typu real-time, dlatego w kolejnym rodziale podejmiemy temat technologii WebSockets. Dowiesz się jakie są jej zalety, a także jak pracować z nią po stronie serwera i klienta. Tutaj czeka nas kolejny praktyczny, niezwykle ciekawy projekt. Wykonamy bowiem czat grupowy. Aplikacja ta pozwoli dowolnej ilości użytkowników podłączyć się do czatu podając swój nick, a następnie wysyłać widoczne dla wszystkich wiadomości. Zaimplementujemy nawet takie rozwiązania jak wyświetlanie statusów o dołączeniu kogoś do czatu, a także o jego opuszczeniu.W przedostatnim rodziale tego kursu podejmiemy tematykę dobrych praktyk pracy z Node.js. Na początku omówimy najważniejsze konstrukcje nowej specyfikacji EcmaScript 2015, które są znakomicie wspierane w Node. Chwilę później omówimy jak korzystanie z Promises uprości, a także ulepszy nasz kod. Wśród dobrych praktyk nie zabraknie również informacji o debugowaniu aplikacji. Zobaczysz sprawdzone sposoby, by znaleźć błędy lub lepiej, krok po kroku, zrozumieć jak działa napisany wcześniej kod. Dowiesz się również jak pracować z błędami, by Twoje aplikacje działały w sposób przewidywalny.
Ostatni rozdział w całości został poświęcony temu, co zwykle jest pomijane w innych materiałach, mianowicie wdrażaniu aplikacji do produkcji. Przez cały ten kurs pracować będziemy lokalnie, lecz kiedy aplikacje są już gotowe, wypadałoby udostępnić je światu. Wdrażanie aplikacji napisanych z użyciem Node.js nie jest jednak tak oczywiste, jak np. wgrywanie WordPress’a u wybranego hostingodawcy. Ty będziesz miał jednak możliwość zobaczyć, jak wdrożyć napisany przez nas grupowy czat na platformie Heroku. Jest to bardzo popularny serwis działający jako PaaS (Platform as a Service). Za darmo będziesz mógł w ciągu kilku chwil uruchomić swoją aplikację.Zobaczysz o co należy zadbać, by wszystko poszło gładko. Serwisy typu PaaS dbają o bardzo wiele aspektów wdrażania i serwowania naszych aplikacji, takich jak bezpieczeństwo oraz nieustanną dostępność. Mają jednak pewne ograniczenia. Z tego powodu, dowiesz się również jak wdrożyć swoją aplikację na serwerze wirtualnym VPS z systemem Ubuntu Server. Takie rozwiązanie daje nam całkowitą kontrolę, ale także obarczone jest większą odpowiedzialnością. Zaczniemy od instalacji na serwerze platformy Node.js, systemu kontroli wersji GIT, a także innych niezbędnych modułów
Następnie zobaczysz jak skonfigurować swoje lokalne środowisko tak, by za pomocą GIT’a wysyłać kod do zdalnego serwera, a potem jednym poleceniem wdrażać go do produkcji. Dowiesz się również jak jednocześnie serwować wersję produkcyjną oraz developmencką. W rodziale tym poruszymy również inne kwestie, takie jak procesy potomne czy tworzenie klastrów. Dzięki tej wiedzy, będziesz mógł maksymalnie wykorzystać dostępne zasoby serwera. Na sam koniec tego kursu rzucimy okiem również na inne, nie przedstawione wcześniej zastosowania Node, a także nakreślimy dalszą drogę nauki w tym zakresie.
Kurs ten jest dla wszystkich osób, które dobrze czują się w technologiach frontendowych, tj. HTML, CSS i JavaScript, a teraz chcą rozpocząć swoją przygodę z back-endem. Im zatem lepiej znasz język JavaScript, tym więcej wyciśniesz z Node, natomiast nie jest wymagana bardzo zaawansowana wiedza z zakresu tego języka.
W tej lekcji pokażę ci, jak działają klastry i jak możemy kilka klastrów uruchomić, aby nasza jednowątkowa
pętla zdarzeń, o której mówiliśmy, była powielona tak naprawdę kilkukrotnie w kilku procesach. I w przypadku
web sellera, abyśmy mogli dystrybuować zapytania na różne procesy, jeżeli będziemy mieli duże obciążenie,
jeżeli chodzi o ilość zapytań na sekundę. Aby zacząć, będziemy potrzebowali Expressa, który wpisałem już
do pliku package.json. Więc wpiszemy npm install, aby to się nam zainstalowano i uruchomimy sobie za moment
serwer nodemonem - index.
Taki serwer nam się uruchomi. Przejdę do kodu i pokażę ci od razu, co tutaj wymyśliłem.
Nie ma nic skomplikowanego. Natomiast console, który będzie wykonywany, czy w zasadzie send a nie console, będzie
nam odsyłał
coś takiego. To zapytanie obsługuje process o PID i tutaj będzie jego identyfikator.
Natomiast pod console.log wpisałem sobie, że ten serwer został uruchomiony i obsługiwany jest przez taki
oto proces.
No więc widzisz, że mamy tutaj proces o numerze 9398 i teraz jeżeli w przeglądarce sobie przejdziemy
pod localhost, też będzie 9398. Czyli tutaj nie ma żadnych czarów, nieważne ile razy odświeżę.
Okazuje się, że za każdym razem ten sam proces, który uruchomił serwer, odsyła nam zapytanie. Jak najbardziej
jest to wszystko poprawne, bo nie mogłoby tutaj być inaczej.
Natomiast teraz pokażę ci, jak możemy zrobić klastry, które pozwolą nam uruchomić cztery instancje takiego
serwera i pomiędzy nimi będzie rozlokowywany tak naprawdę ruch do naszego serwera. Kiedy tutaj będziemy odświeżać,
to będziemy mieli różne PID zwracane.
Jak to zrobić?
Otóż Node.js ma taki wbudowany moduł jak cluster i on tak naprawdę pod spodem korzysta z metody for, którą
mógłbyś znaleźć w child process. Nie pokazywałem jej w lekcji poprzedniej.
Ona pozwala uruchomić proces, ale z takim kanałem komunikacji IPC, gdzie możemy pomiędzy jednym a drugim
procesem się komunikować i przesyłać jakieś dane za pomocą takich eventów, jak miał EventEmitter, czyli
np. on message itd.
Natomiast klastry będziemy chcieli sobie tutaj zainkludować na samej górze, czyli const cluster.
I teraz wytłumaczę ci, na czym sprawa polega.
Otóż możemy się odwołać do metody klastra cluster.fork
i to sprawi, że zostanie uruchomiony nowy proces z tym, co już tutaj mamy. I ten proces będzie mógł teoretycznie
przyjmować połączenia HTTP. Ale nie możemy tego zrobić w tej formie.
Dlaczego? Zobaczysz za moment.
Otóż najpierw musimy zrobić sobie if i sprawdzić, czy ten proces, który uruchomiliśmy za pomocą nodemona,
czy możemy go uruchomić standardowo.
Może to ubiję za pomocą node index.
Czy ten proces uruchomiony jest procesem typu master?
Jak to sprawdzić?
Otóż możemy się odwołać do cluster.isMaster, czyli czy jest to główny proces.
Jeżeli tak, to wykonamy pewien kod.
Jeżeli nie, to będziemy chcieli obsługiwać zapytania, tak jak to widzisz w tym miejscu.
Natomiast to, co sobie u góry zapisaliśmy, czyli express, też będziemy chcieli wywołać tylko raz.
Zatem tutaj może na początek zrobimy console.log("Proces główny") i spróbujemy uruchomić ten serwer. Zobaczysz,
że mamy proces główny, a ten kod nigdy się nie wykonał. Dlatego właśnie, że ten proces, który uruchomiliśmy,
był tym głównym klastrem, czyli blok else nigdy się nie wykonał.
Natomiast zobacz, co stanie się, jeżeli tutaj w tym głównym klastrze wpiszę sobie cluster.fork.
Zobaczymy, czy to, co mamy tutaj, się uruchomi. Okazuje się, że serwer został uruchomiony i możemy pod niego
przejść.
I to, co jest ciekawe, to został on uruchomiony jakby tylko w jednej instancji, ale w nowym procesie.
To oznacza tylko tyle, że gdybyśmy wrócili do tego kodu, to za pierwszym razem on się wczytywał i
cluster.isMaster
było w tym miejscu.
Czyli ten blok się nigdy nie wykonał, ale cluster.fork uruchomiło nową instancję też tego procesu.
Tylko że tam nie mieliśmy już cluster.isMaster
na wartość true, czyli wykonał się ten blok.
Gdybyśmy znowu wpisali cluster.fork, to znowu uruchomimy sobie ten skrypt i znowu będziemy tylko w tym bloku.
Więc teoretycznie uruchomimy kolejny serwer.
Natomiast to, co jest świetne, to chociaż będzie uruchamiany kolejny serwer, to będzie to wszystko obsługiwane
w taki sposób, jakbyśmy mieli tylko jeden.
Dlatego teraz chcę ci pokazać jedną ciekawą technikę, bo po co uruchamiać kolejne procesy node,
po co mieć kilka serwerów?
No właśnie po to, żeby móc większy ruch obsłużyć.
Ale idealnie działa to wtedy, jeżeli uruchomimy tyle procesów node, ile mamy rdzeni procesora.
Gdybym teraz przeszedł do monitora aktywności i wpisał node, to widzisz, że mamy dwa node'y.
Pierwszy to był ten główny proces uruchomiony, a drugi to jest ten cluster.fork.
Natomiast chciałbym sobie uruchomić cztery takie procesy, bo mam cztery rdzenie procesora. Więc jak możemy to
zrobić?
Otóż przejdziemy do modułu, który z kolei nazywa się OS i ci go jeszcze nie pokazywałem.
Zawiera on szereg ciekawych informacji o systemie, na przykład możemy sprawdzić, ile mamy dostępnej pamięci
operacyjnej itd.
Natomiast jedną z ciekawszych rzeczy jest os.cpus.
Jeżeli teraz to zatrzymam, wpiszę node, żeby przejść do REPL-a, tutaj bezpośrednio z OS możemy korzystać.
Więc jeżeli wpiszę cpus, to zwróci nam taką tablicę, która pokazuje informacje o wszystkich rdzeniach
procesora w moim komputerze. I skoro jest to tablica, to ona ma właściwość length.
Czyli jeżeli się do niej odwołam w ten sposób, to zobaczę 4.
A to oznacza, że mam 4 rdzenie w moim systemie. Wyjdę z tego REPL-a w ten sposób.
I teraz skoro wiemy, że możemy się do tego tak odwołać, to będziemy chcieli wykonać pętlę, która nam uruchomi
cztery takie klastry.
Będzie to wyglądać następująco.
Zatem tutaj zapisuję ilość tych rdzeni z modułu OS. Zrobiłem jego require i następnie pętlą
robimy sobie właśnie cluster.fork w ten oto sposób. I teraz będziemy mieli cztery serwery uruchomione
w taki sposób.
Wykonajmy sobie zatem ten kod. Node index, no i widzimy cztery razy console.log, który był tutaj. To znaczy, że cztery serwery
się uruchomiły. Ale zauważ, że każdy z nich ma inny process id.
I w monitorze aktywności mógłbym to potwierdzić.
Mamy teraz pięć procesów. Tak naprawdę jeden proces będzie jakby zarządzał tym wszystkim i będziemy mieli
jeszcze cztery serwery tutaj uruchomione. I teraz pytanie jak to działa?
Po prostu wchodzimy sobie na stronę, możemy odświeżać i tutaj za każdym razem jak odświeżam, to obsługuje nas
tylko jeden proces, dlatego że jest on wolny. Odesłał nam coś i dalej jest wolny, czyli może przyjmować kolejne
zapytania.
Natomiast teraz pokażę ci skrypt, który napisałem, który pozwoli nam obciążyć wszystkie te procesy, abyśmy
rzeczywiście mogli zobaczyć rezultat. I ten skrypt znajduje się już tutaj.
Nazwałem go server_tester i stworzyłem w nim funkcję getData i to jest rzecz, której ci jeszcze nie pokazywałem.
Ale moduł http, który mamy wbudowany w Node, posiada metodę request i możesz nią wysłać dowolne zapytanie.
Mógłbyś tutaj wpisać eduweb.pl i ta funkcja się wykona,
kiedy zostanie jakby odpowiedź odesłana z tego serwera.
Natomiast na response ta odpowiedź będzie streamowana, bo nigdy strona internetowa, czyli kod HTML, w całości
nie jest przesłany, tylko jest streamowany.
Dlatego tworzę zmienną data i za każdym razem, kiedy otrzymujemy jakiś kawałek tej strony, to chcę to
tam dodawać.
Natomiast w momencie kiedy strona w całości się nam wczyta, czyli będziemy na tym streamie mieli end, to chcę wyświetlić
console.log(data).
W ten sposób gdybym tutaj wpisał np. eduweb.pl, to w konsoli zobaczylibyśmy cały kod HTML tej strony. Jednak
my tutaj mamy localhost 8000, czyli dokładnie to, co jest tutaj, a więc data to będzie taki po prostu
string z odpowiednim PID.
Zobaczmy, jak to wygląda. I jeszcze jak uruchamiać tę funkcję cały czas?
Otóż robimy sobie tutaj interval i chcę co 100 milisekund, czyli 10 razy na sekundę, wysyłać takie zapytanie pod
localhost 8080. Natomiast będę chciał po 30 sekundach wyczyścić ten interval, aby przestać wysyłać
takie zapytania.
Łatwo zatem policzyć, że wyślemy 300 takich zapytań.
Czyli tutaj mamy już uruchomiony nasz serwer, a ten plik uruchomimy sobie w osobnym oknie. Otworzę nowe okno
terminala.
Mamy tutaj nową zakładkę i w tej zakładce będziemy w katalogu 44. Uruchomię sobie node server_tester. Wciskam Enter.
I zauważ, że zapytania są wysyłane.
Ale zwróć uwagę na PID, które są zwracane. Za każdym razem praktycznie jest to inne PID.
A to pokazuje nam, że serwery, które uruchomiliśmy, obsługują raz jeden, raz drugi, raz trzeci, raz nawet
czwarty - obsługują wysyłane tam zapytania, które są z tego procesu wysyłane.
Wyśle się ich 300, czyli przez te 30 sekund.
I to wszystko się zakończy.
Możemy też to zakończyć, wciskając Ctrl+C. W ten sposób. Ale, jak widzisz, to nam udowadnia tak naprawdę, że
za każdym razem obsługiwał nasze żądanie inny serwer.
I teraz jakie są tego plusy?
Otóż jeżeli mamy kilka rdzeni w procesorze, ja w tym kursie ci o tym już mówiłem, to możemy wykonywać
kilka rzeczy równolegle i to jest ważne.
Dlatego najlepiej jest uruchamiać tyle takich serwerów, ile mamy rdzeni w procesorze.
Wtedy rzeczywiście kilka rzeczy czy kilka żądań będzie mogło być obsługiwanych równolegle.
Domyślnie działa tutaj strategia, która nazywa się round robin, czyli przydzielamy zasoby raz jednemu serwerowi,
raz drugiemu, raz trzeciemu, raz czwartemu.
Ale można ją również zmienić. Gdyby cię to interesowało,
to poczytaj sobie na stronie dokumentacji klastrów. Ważne jest jeszcze tutaj to, abyśmy nie mieli tak jak tutaj
pomiędzy poszczególnymi serwerami jakichś zmiennych, które miały być dziedziczone. Gdybyśmy np. mieli tutaj zmienną
counter = 1,
to wydaje się, że każde zapytanie, które jest w tym miejscu, ma do niej dostęp.
Ale to co jest ważne i należy na to zwrócić uwagę, to za każdym procesem, który został uruchomiony, w naszym
przypadku cztery takie procesy,
ta przestrzeń będzie niezależna.
Czyli to wszystko będzie od nowa.
A więc jeżeli ktoś będzie wchodził na naszą stronę, tak jak widzisz, tutaj było wiele zapytań wysyłanych,
za każdym razem obsługiwał je inny proces, to każdy z tych procesów będzie miał dostęp do innej zmiennej.
Czyli nie możemy tutaj współdzielić w ten sposób tych danych.
Zatem jeżeli chcemy korzystać z klastrów i z wielu serwerów, to powinniśmy korzystać z baz danych do
przechowywania jakichś informacji.
Podobnie będzie np. z informacjami o sesjach. Istnieje świetna baza danych która nazywa się redis.
I ona działa całkowicie w pamięci RAM, czyli jest bardzo szybka.
Możesz o niej więcej poczytać i w Node.js również z niej korzystać. Wtedy gdybyśmy tutaj odwoływali się
do tej bazy danych, coś byśmy do niej zapisali,
zaraz następne połączenie byłoby obsłużone przez inny proces, ale on też miałby do niej dostęp, bo mógłby
się do niej zalogować i coś z niej np. pobrać.
Więc mam nadzieję, że to rozumiesz.
A teraz na koniec pokażę ci jeszcze jedną ciekawą rzecz. Mianowicie tutaj, gdzie tworzymy nowe klastry,
czyli cluster.fork,
możemy do takiego klastra przypisać zdarzenie on exit. Czyli kiedy on po prostu upadnie z jakiegoś powodu,
zrobimy to w ten sposób clister.on("exit") i wykonamy sobie funkcję. Czyli kiedy taki proces padnie, bo np.
będzie w nim jakiś błąd albo my go ręcznie ubijemy, to możemy wtedy uruchomić sobie nowy.
Piszemy cluster.fork i dzięki temu zawsze będziemy mieli uruchomione w moim przypadku cztery procesy.
Bo na początku będą one cztery, ale kiedy któryś z nich padnie, to wykona się ta funkcja i uruchomimy
sobie kolejny, czyli znowu będziemy mieli cztery. Napiszmy sobie tutaj jeszcze console.log. Napisałem: Proces
o PID -
i tutaj jego PID - zakończył się. Uruchamiam nowy.
Teraz to będziemy mogli przetestować raz jeszcze w tym miejscu. Ten serwer sobie zakończymy, uruchomimy od nowa.
Mamy cztery uruchomione serwery. Teraz przejdziemy tutaj i będziemy chcieli go przetestować. Wywołam to sobie.
Przez 30 sekund będzie działać, więc w tym czasie zdążę przejść tutaj i zauważ, że mamy kilka tych procesów.
Spróbuję któryś z nich po prostu ubić w taki standardowy sposób. Bylebym tylko trafił na ten, który jest
serwerem. Więc zobaczmy. Jakiś 9642 na przykład tam widziałem, czyli jest to ten. Zakończę jego działanie
i zobaczymy gdzieś console.log później w tym miejscu, że proces o PID 9640 zakończył się. Uruchamiam
nowy. I mamy tutaj serwer został uruchomiony.
Czyli ten console.log wykonał się znowu stąd. Bo cały ten plik jakby od nowa się wczytał, is.Master miało wartość
false, czyli przeszliśmy w to miejsce i uruchomił się nowy serwer.
Tak to będzie działać. Tutaj 30 sekund już minęło.
Gdybym raz jeszcze to wywołał, raz jeszcze możemy dowolny serwer sobie ubić.
I tutaj będziemy widzieć informację, że znowu któryś serwer padł i uruchomiliśmy nowy.
Jak zatem widzisz, tworzenie takich klastrów jest bardzo proste w Node.js.
Natomiast po raz kolejny muszę ci powiedzieć, że w npm znajdziesz masę modułów, które jeszcze bardziej
to ułatwią i sprawią, że to wszystko będzie jeszcze lepiej działać.