Node.js w akcji

Poznaj potencjał Node.js! JavaScript to jeden z niewielu języków programowania w historii, który wzbudzał i wciąż wzbudz

921 93 7MB

Polish Pages [516] Year 2014

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Node.js w akcji

Table of contents :
Wstęp
Przedmowa
Podziękowania
Mike Cantelon
Marc Harter
Nathan Rajlich
O książce
Mapa drogowa
Konwencje zastosowane w kodzie i materiały do pobrania
Forum Author Online
Część I Podstawy Node
Rozdział 1. Witamy w Node.js
1.1. Node jest zbudowane w oparciu o JavaScript
1.2. Asynchroniczna i oparta na zdarzeniach: przeglądarka internetowa
1.3. Asynchroniczny i oparty na zdarzeniach: serwer
1.4. Aplikacje DIRT
1.5. Domyślna aplikacja jest typu DIRT
1.5.1. Prosty przykład aplikacji asynchronicznej
1.5.2. Serwer HTTP
1.5.3. Strumieniowanie danych
1.6. Podsumowanie
Rozdział 2. Tworzenie aplikacji wielopokojowego czatu
2.1. Ogólny opis aplikacji
2.2. Wymagania aplikacji i konfiguracja początkowa
2.2.1. Obsługa HTTP i WebSocket
2.2.2. Tworzenie struktury plików aplikacji
2.2.3. Wskazanie zależności
2.2.4. Instalacja zależności
2.3. Udostępnianie plików HTML, CSS i kodu JavaScript działającego po stronie klienta
2.3.1. Tworzenie podstawowego serwera plików statycznych
Wysyłanie danych pliku i odpowiedzi w postaci błędów
Tworzenie serwera HTTP
Uruchomienie serwera HTTP
2.3.2. Dodanie plików HTML i CSS
2.4. Obsługa wiadomości czatu za pomocą biblioteki Socket.IO
2.4.1. Konfiguracja serwera Socket.IO
Utworzenie logiki połączenia
2.4.2. Obsługa zdarzeń oraz scenariuszy w aplikacji
Przypisanie nazwy gościa
Dołączanie do pokoju
Obsługa żądań zmiany nazwy użytkownika
Wysyłanie wiadomości czatu
Tworzenie pokoju
Obsługa rozłączenia użytkownika
2.5. Użycie kodu JavaScript działającego po stronie klienta do utworzenia interfejsu użytkownika aplikacji
2.5.1. Przekazywanie do serwera wiadomości oraz żądań zmiany pokoju lub nazwy użytkownika
2.5.2. Wyświetlenie w interfejsie użytkownika wiadomości i listy dostępnych pokoi
2.6. Podsumowanie
Rozdział 3. Podstawy programowania w Node
3.1. Organizacja i wielokrotne użycie kodu Node
3.1.1. Tworzenie modułu
3.1.2. Dostrajanie tworzenia modułu za pomocą module.exports
3.1.3. Wielokrotne użycie modułów za pomocą katalogu node_modules
3.1.4. Zastrzeżenia
3.2. Techniki programowania asynchronicznego
3.2.1. Użycie wywołań zwrotnych do obsługi zdarzeń jednorazowych
3.2.2. Użycie emitera zdarzeń do obsługi powtarzających się zdarzeń
Przykład emitera zdarzeń
Udzielanie odpowiedzi na zdarzenie, które powinno wystąpić tylko jednokrotnie
Tworzenie emitera zdarzeń — przykład oparty na publikacji i subskrypcji
Rozbudowa emitera zdarzeń — przykład obserwatora pliku
3.2.3. Wyzwania pojawiające się podczas programowania asynchronicznego
3.3. Sekwencja logiki asynchronicznej
3.3.1. Kiedy stosować szeregową kontrolę przepływu?
3.3.2. Implementacja szeregowej kontroli przepływu
3.3.3. Implementacja równoległej kontroli przepływu
3.3.4. Użycie narzędzi opracowanych przez społeczność
3.4. Podsumowanie
Część II Tworzenie aplikacji sieciowych w Node
Rozdział 4. Tworzenie aplikacji sieciowej w Node
4.1. Podstawy dotyczące serwera HTTP
4.1.1. Jak przychodzące żądania HTTP są przez Node przedstawiane programiście?
4.1.2. Prosty serwer HTTP odpowiadający komunikatem „Witaj, świecie”
4.1.3. Odczyt nagłówków żądania i zdefiniowanie nagłówków odpowiedzi
4.1.4. Ustawienie kodu stanu odpowiedzi HTTP
4.2. Tworzenie usługi sieciowej RESTful
4.2.1. Tworzenie zasobów za pomocą żądań POST
4.2.2. Pobieranie zasobów za pomocą żądania GET
Zdefiniowanie nagłówka Content-Length
4.2.3. Usunięcie zasobu za pomocą żądania DELETE
4.3. Udostępnianie plików statycznych
4.3.1. Tworzenie serwera plików statycznych
Optymalizacja transferu danych za pomocą Stream#pipe
4.3.2. Obsługa błędów serwera
4.3.3. Wyprzedzająca obsługa błędów za pomocą wywołania fs.stat
4.4. Akceptacja danych wejściowych użytkownika przekazanych za pomocą formularza sieciowego
4.4.1. Obsługa wysłanych pól formularza sieciowego
Moduł querystring
4.4.2. Obsługa przekazanych plików za pomocą formidable
4.4.3. Sprawdzanie postępu operacji przekazywania plików
4.5. Zabezpieczanie aplikacji dzięki użyciu protokołu HTTPS
4.6. Podsumowanie
Rozdział 5. Przechowywanie danych aplikacji Node
5.1. Niewymagający serwera magazyn danych
5.1.1. Magazyn danych w pamięci
5.1.2. Magazyn danych oparty na plikach
Utworzenie logiki początkowej
Zdefiniowanie funkcji pomocniczej do pobierania zadań
Zdefiniowanie funkcji pomocniczej do przechowywania zadań
5.2. System zarządzania relacyjną bazą danych
5.2.1. MySQL
Użycie MySQL do utworzenia aplikacji śledzącej wykonywanie zadań
Utworzenie logiki aplikacji
Tworzenie funkcji pomocniczych odpowiedzialnych za wysyłanie kodu HTML, tworzenie formularzy sieciowych i pobieranie danych z formularzy
Dodanie danych za pomocą MySQL
Usuwanie danych MySQL
Uaktualnienie danych MySQL
Pobieranie danych MySQL
Generowanie rekordów MySQL
Generowanie formularzy HTML
Wypróbowanie aplikacji
5.2.2. PostgreSQL
Nawiązanie połączenia z PostgreSQL
Wstawienie rekordu do tabeli bazy danych
Utworzenie zapytania zwracającego wynik
5.3. Bazy danych typu NoSQL
5.3.1. Redis
Nawiązywanie połączenia z serwerem Redis
Praca z danymi bazy Redis
Przechowywanie i pobieranie wartości za pomocą tabeli hash
Przechowywanie i pobieranie danych za pomocą listy
Przechowywanie i pobieranie danych za pomocą zbiorów
Dostarczanie danych za pomocą kanałów
Maksymalizacja wydajności Node_Redis
5.3.2. MongoDB
Nawiązanie połączenia z MongoDB
Uzyskanie dostępu do kolekcji MongoDB
Wstawienie dokumentu do kolekcji
Uaktualnienie danych za pomocą identyfikatora dokumentu
Wyszukiwanie dokumentów
Usuwanie dokumentów
5.3.3. Mongoose
Otworzenie i zamknięcie połączenia
Rejestracja schematu
Dodanie zadania
Wyszukiwanie dokumentu
Uaktualnianie dokumentu
Usuwanie dokumentu
5.4. Podsumowanie
Rozdział 6. Framework Connect
6.1. Konfiguracja aplikacji Connect
6.2. Jak działa metoda pośrednicząca frameworka Connect?
6.2.1. Metody pośredniczące wyświetlające żądanie
6.2.2. Metoda pośrednicząca udzielająca odpowiedzi w postaci komunikatu „Witaj, świecie”
6.3. Dlaczego kolejność metod pośredniczących ma znaczenie?
6.3.1. Kiedy metoda pośrednicząca nie wywołuje next
6.3.2. Użycie kolejności metod pośredniczących do przeprowadzenia uwierzytelnienia
6.4. Montowanie metody pośredniczącej i serwera
6.4.1. Metody pośredniczące przeprowadzające uwierzytelnianie
6.4.2. Metoda pośrednicząca wyświetlająca panel administracyjny
Przetestowanie całości
6.5. Tworzenie konfigurowalnej metody pośredniczącej
6.5.1. Tworzenie konfigurowalnej metody pośredniczącej logger
6.5.2. Tworzenie metody pośredniczącej router
6.5.3. Tworzenie metody pośredniczącej przeznaczonej do przepisywania adresów URL
6.6. Użycie metody pośredniczącej do obsługi błędów
6.6.1. Domyślna obsługa błędów w Connect
6.6.2. Samodzielna obsługa błędów aplikacji
6.6.3. Użycie wielu metod pośredniczących przeznaczonych do obsługi błędów
Implementacja metody pośredniczącej hello
Implementacja metody pośredniczącej users
Implementacja metody pośredniczącej pets
Implementacja metody pośredniczącej errorHandler
Implementacja metody pośredniczącej errorPage
6.7. Podsumowanie
Rozdział 7. Metody pośredniczące frameworka Connect
7.1. Metody pośredniczące przeznaczone do przetwarzania plików cookie, danych żądań i ciągów tekstowych zapytań
7.1.1. cookieParser
Podstawowy sposób użycia
Zwykłe cookie
Podpisane cookie
Cookie w formacie JSON
Ustawienie cookie wychodzących
7.1.2. bodyParser
Podstawowy sposób użycia
Przetwarzanie danych JSON
Przetwarzanie zwykłych danych formularza sieciowego
Przetwarzanie wieloczęściowych danych formularza sieciowego
7.1.3. limit
Dlaczego metoda pośrednicząca limit
Podstawowy sposób użycia
Opakowanie metody pośredniczącej limit
7.1.4. query
Podstawowy sposób użycia
7.2. Metody pośredniczące implementujące podstawowe funkcje wymagane przez aplikację sieciową
7.2.1. logger
Podstawowy sposób użycia
Dostosowanie do własnych potrzeb formatu rejestracji danych
Opcje metody pośredniczącej logger
7.2.2. favicon
Podstawowy sposób użycia
7.2.3. methodOverride
Podstawowy sposób użycia
Uzyskanie dostępu do oryginalnej właściwości req.method
7.2.4. vhost
Podstawowy sposób użycia
Użycie wielu egzemplarzy vhost
7.2.5. session
Podstawowy sposób użycia
Ustawienie czasu wygaśnięcia ważności sesji
Praca z danymi sesji
Praca z plikami cookie sesji
Magazyn danych sesji
7.3. Metody pośredniczące zapewniające bezpieczeństwo aplikacji sieciowej
7.3.1. basicAuth
Podstawowy sposób użycia
Przekazanie funkcji wywołania zwrotnego
Przekazanie asynchronicznej funkcji wywołania zwrotnego
Przykład z użyciem polecenia curl
7.3.2. csrf
Podstawowy sposób użycia
7.3.3. errorHandler
Podstawowy sposób użycia
Komunikat błędu w formacie HTML
Komunikat błędu w formacie zwykłego tekstu
Komunikat błędu w formacie JSON
7.4. Metody pośredniczące przeznaczone do udostępniania plików statycznych
7.4.1. static
Podstawowy sposób użycia
Użycie metody static
Bezwzględne kontra względne ścieżki dostępu do katalogów
Udostępnianie pliku index.html, gdy żądanie dotyczy katalogu
7.4.2. compress
Podstawowy sposób użycia
Użycie własnej funkcji filtrującej
Określenie poziomu kompresji i pamięci
7.4.3. directory
Podstawowy sposób użycia
Podstawowy sposób użycia
7.5. Podsumowanie
Rozdział 8. Framework Express
8.1. Utworzenie szkieletu aplikacji
8.1.1. Globalna instalacja frameworka Express
8.1.2. Generowanie aplikacji
8.1.3. Poznawanie aplikacji
8.2. Konfiguracja frameworka Express i tworzonej aplikacji
8.2.1. Konfiguracja na podstawie środowiska
8.3. Generowanie widoków aplikacji Express
8.3.1. Konfiguracja systemu widoków
Zmiana katalogu wyszukiwania
Domyślny silnik szablonów
Buforowanie widoku
8.3.2. Wyszukiwanie widoku
8.3.3. Udostępnianie danych widokom
Tworzenie widoku wyświetlającego listę zdjęć
Metody udostępniania danych widokom
8.4. Obsługa formularzy i przekazywania plików
8.4.1. Implementacja modelu zdjęcia
8.4.2. Tworzenie formularza przeznaczonego do przekazywania zdjęć
Utworzenie formularza
Dodanie trasy dla strony przeznaczonej do przekazywania zdjęć
Obsługa wysyłania danych formularza
8.4.3. Wyświetlenie listy przekazanych zdjęć
8.5. Obsługa pobierania zasobów
8.5.1. Tworzenie trasy dla pobierania zdjęć
8.5.2. Implementacja trasy pobierania zdjęcia
Rozpoczęcie pobierania przez przeglądarkę
Ustawienie nazwy pobieranego pliku
8.6. Podsumowanie
Rozdział 9. Zaawansowane użycie frameworka Express
9.1. Uwierzytelnianie użytkowników
9.1.1. Zapisywanie i wczytywanie użytkowników
Tworzenie pliku package.json
Tworzenie modelu User
Zapis użytkownika w bazie danych Redis
Zabezpieczanie hasła użytkownika
Testowanie logiki odpowiedzialnej za zapis użytkownika
Pobieranie danych użytkownika
Uwierzytelnianie użytkownika
9.1.2. Rejestrowanie nowego użytkownika
Dodanie tras rejestracji
Tworzenie formularza rejestracji
Udzielanie odpowiedzi użytkownikowi
Trwałe przechowywanie komunikatów w sesji
Implementacja rejestracji użytkownika
9.1.3. Logowanie zarejestrowanych użytkowników
Wyświetlenie formularza logowania
Uwierzytelnienie logowania
Utworzenie menu dla użytkowników uwierzytelnionych i anonimowych
9.1.4. Metoda pośrednicząca przeznaczona do wczytywania użytkownika
9.2. Zaawansowane techniki routingu
9.2.1. Weryfikacja użytkownika podczas przesyłania treści
Utworzenie modelu postu
Dodanie tras powiązanych z postami
Dodanie strony wyświetlającej listę postów
Utworzenie formularza postu
Implementacja tworzenia postu
9.2.2. Metoda pośrednicząca charakterystyczna dla trasy
Weryfikacja formularza za pomocą metody pośredniczącej przeznaczonej dla trasy
Utworzenie elastycznej metody pośredniczącej przeznaczonej do przeprowadzania weryfikacji
9.2.3. Implementacja stronicowania
Projektowanie API stronicowania
Implementacja metody pośredniczącej przeznaczonej do obsługi stronicowania
Użycie stronicowania w trasie
Utworzenie szablonu dla łączy mechanizmu stronicowania
Dodanie łączy stronicowania w szablonie
Włączenie czystych adresów URL dla stronicowania
9.3. Utworzenie publicznego API REST
9.3.1. Projekt API
9.3.2. Dodanie uwierzytelnienia podstawowego
9.3.3. Implementacja routingu
Testowanie operacji pobierania danych użytkownika
Usunięcie danych wrażliwych użytkownika
Dodanie postów
Dodanie obsługi wyświetlania listy wpisów
9.3.4. Włączenie negocjacji treści
Implementacja negocjacji treści
Udzielenie odpowiedzi w postaci XML
9.4. Obsługa błędów
9.4.1. Obsługa błędów 404
Dodanie trasy pozwalającej na udzielenie odpowiedzi informującej o błędzie
Tworzenie szablonu dla strony błędu
Włączenie metody pośredniczącej
9.4.2. Obsługa błędów
Użycie trasy warunkowej do przetestowania stron błędów
Implementacja procedury obsługi błędów
Utworzenie szablonu strony błędu
Włączenie metody pośredniczącej
9.5. Podsumowanie
Rozdział 10. Testowanie aplikacji Node
10.1. Testy jednostkowe
10.1.1. Moduł assert
Prosty przykład
Użycie asercji eqal
Użycie asercji notEqual
Użycie asercji dodatkowych strictEqual
Użycie asercji ok
Sprawdzenie, czy zgłaszane komunikaty błędów są poprawne
Dodanie logiki przeznaczonej do uruchamiania testów
10.1.2. Framework nodeunit
Instalacja nodeunit
Testowanie aplikacji Node za pomocą frameworka nodeunit
10.1.3. Mocha
Testowanie aplikacji Node za pomocą Mocha
Zdefiniowanie konfiguracji i czyszczenie logiki za pomocą zaczepów Mocha
Testowanie logiki asynchronicznej
10.1.4. Framework Vows
Testowanie logiki aplikacji za pomocą frameworka Vows
10.1.5. Biblioteka should.js
Testowanie funkcjonalności modułu za pomocą biblioteki should.js
10.2. Testy akceptacyjne
10.2.1. Tobi
Testowanie aplikacji sieciowych za pomocą Tobi
10.2.2. Soda
Instalacja frameworka Soda i serwera Selenium
Testowanie aplikacji sieciowej za pomocą Soda i Selenium
Testowanie aplikacji sieciowej za pomocą Soda i Sauce Labs
10.3. Podsumowanie
Rozdział 11. Szablony w aplikacji sieciowej
11.1. Użycie szablonów w celu zachowania przejrzystości kodu
11.1.1. Szablon w akcji
Wygenerowanie kodu HTML bez użycia szablonu
Wygenerowanie kodu HTML z użyciem szablonu
11.2. Silnik szablonów Embedded JavaScript
11.2.1. Tworzenie szablonu
Zmiana znaczenia znaków
11.2.2. Praca z danymi szablonu za pomocą filtrów EJS
Filtry obsługujące wybór
Filtry przeznaczone do zmiany wielkości znaków
Filtry przeznaczone do pracy z tekstem
Filtry przeprowadzające sortowanie
Filtr map
Tworzenie własnych filtrów
11.2.3. Integracja EJS w aplikacji
Buforowanie szablonów EJS
11.2.4. Użycie EJS w aplikacjach działających po stronie klienta
11.3. Użycie języka szablonów Mustache wraz z silnikiem Hogan
11.3.1. Tworzenie szablonu
11.3.2. Znaczniki Mustache
Wyświetlanie prostych wartości
Sekcje: iteracja przez wiele wartości
Sekcje odwrócone: domyślny kod HTML, gdy wartość nie istnieje
Sekcja lambda: własna funkcjonalność w blokach sekcji
Partials: wielokrotne użycie szablonów w innych szablonach
11.3.3. Dostosowanie szablonu Hogan do własnych potrzeb
11.4. Szablony Jade
11.4.1. Podstawy szablonów Jade
Podawanie atrybutów znacznika
Podanie treści znacznika
Zachowanie organizacji dzięki rozwinięciu bloku
Umieszczanie danych w szablonach Jade
11.4.2. Logika w szablonach Jade
Użycie JavaScript w szablonach Jade
Iteracja przez obiekty i tablice
Warunkowe wygenerowanie kodu szablonu
Użycie poleceń case w Jade
11.4.3. Organizacja szablonów Jade
Strukturyzacja wielu szablonów za pomocą ich dziedziczenia
Implementacja układu za pomocą poprzedzenia blokiem lub dołączenia bloku
Dołączanie szablonu
Wielokrotne użycie logiki szablonu za pomocą polecenia mixin
11.5. Podsumowanie
Część III Co dalej?
Rozdział 12. Wdrażanie aplikacji Node i zapewnienie bezawaryjnego działania
12.1. Hosting aplikacji Node
12.1.1. Serwery dedykowane i VPS
12.1.2. Hosting w chmurze
Amazon Web Services
Rackspace Cloud
12.2. Podstawy wdrożenia
12.2.1. Wdrożenie z repozytorium Git
12.2.2. Zapewnienie działania aplikacji Node
12.3. Maksymalizacja wydajności i czasu bezawaryjnego działania aplikacji
12.3.1. Zapewnienie działania aplikacji za pomocą Upstart
12.3.2. API klastra — wykorzystanie zalety w postaci wielu rdzeni
12.3.3. Proxy i hosting plików statycznych
12.4. Podsumowanie
Rozdział 13. Nie tylko serwery WWW
13.1. Biblioteka Socket.IO
13.1.1. Tworzenie minimalnej aplikacji Socket.IO
Wypróbowanie aplikacji
13.1.2. Użycie biblioteki Socket.IO do odświeżenia strony i stylów CSS
Wypróbowanie aplikacji
13.1.3. Inne zastosowania dla biblioteki Socket.IO
13.2. Dokładniejsze omówienie sieci TCP/IP
13.2.1. Praca z buforami i danymi binarnymi
Dane tekstowe kontra binarne
13.2.2. Tworzenie serwera TCP
Zapis danych
Odczyt danych
Połączenie dwóch strumieni za pomocą socket.pipe
Obsługa nieeleganckiego zamknięcia połączenia
Zebranie wszystkiego w całość
13.2.3. Tworzenie klienta TCP
13.3. Narzędzia przeznaczone do pracy z systemem operacyjnym
13.3.1. Obiekt process, czyli globalny wzorzec Singleton
Użycie process.env do pobierania i ustawiania zmiennych środowiskowych
Zdarzenia specjalne emitowane przez obiekt proces
Przechwytywanie sygnałów wysyłanych procesowi
13.3.2. Użycie modułu filesystem
Przenoszenie pliku
Monitorowanie katalogu lub pliku pod kątem zmian
Użycie opracowanych przez społeczność modułów fstream i filed
13.3.3. Tworzenie procesów zewnętrznych
Buforowanie za pomocą cp.exec
Tworzenie poleceń za pomocą interfejsu Stream i cp.spawn
Rozkład obciążenia za pomocą cp.fork
13.4. Tworzenie narzędzi powłoki
13.4.1. Przetwarzanie argumentów podanych w powłoce
13.4.2. Praca ze standardowym wejściem i wyjściem
Zapis danych wyjściowych za pomocą process.stdout
Odczyt danych wejściowych za pomocą process.stdin
Rejestracja danych diagnostycznych za pomocą process.stderr
13.4.3. Dodanie koloru do danych wyjściowych
Tworzenie i zapis znaków sterujących ANSI
Formatowanie koloru tekstu za pomocą ansi.js
Formatowanie koloru tła za pomocą ansi.js
13.5. Podsumowanie
Rozdział 14. Ekosystem Node
14.1. Dostępne w internecie zasoby dla programistów Node
14.1.1. Node i odniesienia do modułów
14.1.2. Grupy Google
14.1.3. IRC
14.1.4. Zgłaszanie problemów w serwisie GitHub
14.2. Serwis GitHub
14.2.1. Rozpoczęcie pracy z GitHub
Konfiguracja Git i rejestracja GitHub
Dostarczenie GitHub klucza publicznego SSH
14.2.2. Dodanie projektu do GitHub
Utworzenie repozytorium GitHub
Konfiguracja pustego repozytorium Git
Dodanie plików do repozytorium Git
Przekazanie repozytorium Git do serwisu GitHub
14.2.3. Współpraca przez serwis GitHub
14.3. Przekazanie własnego modułu do repozytorium npm
14.3.1. Przygotowanie pakietu
14.3.2. Przygotowanie specyfikacji pakietu
14.3.3. Testowanie i publikowanie pakietu
Testowanie instalacji pakietu
Dodanie użytkownika npm
Publikacja w repozytorium npm
14.4. Podsumowanie
Dodatek A Instalacja Node i dodatki opracowane przez społeczność
A.1. Instalacja w systemie OS X
A.1.1. Instalacja za pomocą Homebrew
A.2. Instalacja w systemie Windows
A.3. Instalacja w systemie Linux
A.3.1. Przygotowania do instalacji w Ubuntu
A.3.2. Przygotowania do instalacji w CentOS
A.4. Kompilacja Node
A.5. Używanie menedżera pakietów Node
A.5.1. Wyszukiwanie pakietów
A.5.2. Instalacja pakietu
A.5.3. Przeglądanie dokumentacji i kodu pakietu
Dodatek B Debugowanie Node
B.1. Analiza kodu za pomocą JSHint
B.2. Dane wyjściowe debugowania
B.2.1. Debugowanie za pomocą modułu console
Wyświetlanie informacji o stanie aplikacji
Dane wyjściowe dotyczące pomiaru czasu
Wyświetlanie stosu wywołań
B.2.2. Użycie modułu debug do zarządzania danymi wyjściowymi procesu debugowania
B.3. Debuger wbudowany w Node
B.3.1. Nawigacja po debugerze
B.3.2. Analiza i zmiana stanu w debugerze
B.4. Inspektor Node
B.4.1. Uruchomienie inspektora Node
B.4.2. Nawigacja po inspektorze Node
B.4.3. Przeglądanie stanu w inspektorze Node
Dodatek C Rozszerzenie i konfiguracja frameworka Express
C.1. Rozszerzenie frameworka Express
C.1.1. Rejestracja szablonów silników
C.1.2. Szablony i projekt consolidate.js
C.1.3. Frameworki i rozszerzenia Express
Express-Expose
Express-Resource
C.2. Konfiguracja zaawansowana
C.2.1. Modyfikacja odpowiedzi JSON
C.2.2. Formatowanie odpowiedzi JSON
C.2.3. Zaufanie nagłówkom odwrotnego proxy

Citation preview

Wstęp Napisanie książki o Node.js stanowi nie lada wyzwanie. Node.js to stosunkowo nowa platforma, która właśnie osiąga stabilność. Jej podstawowe komponenty są stale rozwijane, a społeczność użytkowników opracowuje moduły w tak zawrotnym tempie, że nikt nie jest już w stanie śledzić tego na bieżąco. Dodatkowo społeczność użytkowników Node nadal się „klaruje”. Jedynym sposobem uchwycenia tej nieustannie zmieniającej się platformy i opracowania książki jest skoncentrowanie się na esencji, czyli na określeniu, czym tak naprawdę jest platforma Node i dlaczego odniosła taki sukces. W dokładnie ten sposób postąpili weterani zajmujący się Node.js. Mike Cantelon to niezwykle aktywny członek społeczności Node, prowadzi eksperymenty i wykłady poświęcone tej platformie. Posiada niezwykłe wyczucie i potrafi właściwie ocenić, do jakich celów zastosowanie Node będzie odpowiednie i co ważniejsze, do jakich zupełnie się nie nadaje. Z kolei T.J. Holowaychuk jest jednym z najbardziej twórczych autorów modułów Node.js, opracował między innymi niezwykle popularny framework Express. Natomiast Nathan Rajlich (wielu zna go bardziej jako TooTallNate) przez pewien czas brał udział w tworzeniu Node i aktywnie uczestniczy w rozwoju platformy. W niniejszej książce wykorzystano cenne doświadczenia wymienionych osób. Czytelnik poznaje platformę Node od samego początku, od pierwszego kroku, jakim jest instalacja w systemie, aż po tworzenie, debugowanie i wdrażanie aplikacji. Dzięki tej książce dowiesz się, dlaczego platforma Node jest tak interesująca, i będziesz potrafił wykorzystać przekazaną przez autorów fachową wiedzę do podejmowania decyzji we własnych projektach opartych na Node. Co ważniejsze, materiał przedstawiony w książce został dobrany w taki sposób, aby stopniowo przechodzić od najprostszych zagadnień do coraz bardziej złożonych i zaawansowanych, zawsze bazując na zdobytej wcześniej wiedzy. Node to prawdziwa, wzbijająca się wysoko rakieta, a autorzy książki dołożyli wszelkich starań, aby jak najlepiej przygotować czytelnika do podróży nią. Niniejszą książkę potraktuj jako pewnego rodzaju wstęp do samodzielnego odkrywania nowych horyzontów. Issac Z. Schlueter autor menedżera npm lider projektu Node.js

Przedmowa Na początku 2011 roku, kiedy wydawnictwo Manning przedstawiło nam pomysł napisania książki poświęconej platformie Node.js, społeczność Node przedstawiała się zupełnie inaczej niż obecnie. Była niewielka i mimo że platforma Node budziła coraz większe zainteresowanie, to inni programiści nadal uznawali ją za stosunkowo młodą i ryzykowną technologię. Wówczas jeszcze nikt nie napisał książki o Node i choć już sam pomysł wydawał się zniechęcający, postanowiliśmy jednak podjąć wyzwanie. Biorąc pod uwagę nasze skłonności do programowania, chcieliśmy opracować książkę, której tematyka byłaby skoncentrowana nie tylko na samym użyciu platformy Node do tworzenia aplikacji sieciowych, ale również przedstawiała inne, potencjalnie interesujące jej zastosowania. Naszym celem był pokazanie programistom tworzącym aplikacje sieciowe za pomocą konwencjonalnych technologii, że użycie platformy Node to praktycznie spełnienie snu o programowaniu asynchronicznym do serwera. Podczas naszej ponaddwuletniej pracy nad książką technologia nieustannie się zmieniała i rozwijała, co wymuszało wprowadzanie stosownych zmian w tekście. Warto w tym miejscu dodać, że wraz z rozwojem technologii ewoluowała także społeczność. Obecnie jest dużo liczniejsza, a wiele znanych firm przekonało się do platformy Node. Ta książka jest świetną pozycją dla wszystkich programistów aplikacji sieciowych, którzy chcą spróbować czegoś nowego. To również dobry czas na poznawanie platformy Node. Mamy nadzieję, że książkę uznasz za doskonały materiał do szybkiej i przyjemnej nauki.

Podziękowania Szczególne podziękowania kierujemy do wszystkich wspaniałych pracowników wydawnictwa Manning, którzy przyczynili się do powstania tej książki. Dziękujemy Renae Gregoire za pomoc językową oraz czuwanie nad przejrzystością i jakością przygotowanego przez nas materiału. Bert Bates opracował układ graficzny książki przeznaczony do wyrażenia w niej różnych koncepcji. Marjan Bace i Michael Stephens wierzyli w nas i pomyślność całego przedsięwzięcia oraz pomagali na każdym etapie projektu, za co należą im się słowa podziękowania. Pragniemy również wyrazić wdzięczność zespołowi redakcyjnemu, produkcyjnemu oraz technicznemu wydawnictwa Manning za fantastyczną współpracę. Wiele osób zapoznawało się z tekstem książki na różnych etapach jej powstawania, w tym miejscu dziękujemy im za cenne uwagi. To obejmuje również czytelników będących uczestnikami programu MEAP1, którzy na poświęconym książce forum umieszczali swoje komentarze i propozycje poprawek. Podziękowania składamy także wymienionym niżej osobom wielokrotnie czytającym tekst, których spostrzeżenia i komentarze pomogły uczynić książkę bardziej pomocną i fachową. Są to: Alex Madurell, Bert Thomas, Bradley Meck, Braj Panda, Brian L. Cooley, Brian Del Vecchio, Brian Dillard, Brian Ehmann, Brian Falk, Daniel Bretoi, Gary Ewan Park, Jeremy Martin, Jeroen Nouws, Jeroen Trappers, Kassandra Perch, Kevin Baister, Michael Piscatello, Patrick Steger, Paul Stack i Scott Banachowski. Podziękowania kierujemy także do Valentina Crettaza i Michaela Levina za dokładną korektę techniczną ostatecznej wersji tekstu na krótko przed jego oddaniem do druku. I wreszcie ogromne podziękowania składamy Isaacowi Schlueterowi, liderowi projektu Node, za napisanie wstępu do książki.

Mike Cantelon Chciałbym podziękować mojemu przyjacielowi Joshui Paulowi za wprowadzenie mnie do świata techniki, pokazanie oprogramowania typu open source, a także za zachęcanie do napisania tej książki. Dziękuję też mojemu partnerowi Malcolmowi za wsparcie okazane podczas pracy nad książką oraz za cierpliwość, gdy pisanie pochłaniało mnie praktycznie całkowicie. Wielkie podziękowania należą się również moim rodzicom za rozbudzanie we mnie pasji do kreatywności i ciągłych poszukiwań oraz za cierpliwe znoszenie mojej dziecięcej obsesji na punkcie komputerów 8-bitowych. Dziękuję też moim dziadkom za podarowanie mi komputera Commodore 64, dzięki któremu połknąłem bakcyla programowania i tak mi zostało do dzisiaj.

Znajomość tematu i fantastyczne poczucie humoru T.J. i Nathana okazały się nieocenione w trakcie pisania tej książki. Dziękuję im za wiarę w pomyślność całego przedsięwzięcia oraz za chęć współpracy. Marc Harter również okazał się niezwykle pomocy i podjął się herkulesowego zadania edycji, korekty i przygotowania treści łączącej poszczególne fragmenty książki w całość.

Marc Harter Dziękuję Ryanowi Dahlowi, który kilka lat temu zainspirował mnie do tego, aby na poważnie zająć się programowaniem w języku JavaScript po stronie serwera. Podziękowania kierują również do Bena Noordhuisa, nieocenionego źródła informacji o wewnętrznych sposobach działania Node. Dziękuję też Bert Bates, który we mnie wierzył, pobudzał mnie i mobilizował oraz był zawsze pomocny podczas całego procesu pisania książki. Podziękowania składam również Mike’owi, Nate i T.J. za przyjęcie mnie do zespołu niemal w ostatniej chwili — to był prawdziwy zaszczyt móc z wami pracować. Szczególne podziękowania kieruję do Hannah — mojej żony i przyjaciółki — której odwaga i życzliwość pomogły mi przebrnąć przez to nowe przedsięwzięcie.

Nathan Rajlich Na początku chciałbym podziękować Guillermo Rauch za przyjęcie mnie i pomoc w znalezieniu własnego miejsca w społeczności Node.js. Ponadto dziękuję Davidowi Blicksteinowi za zachęcanie do udziału w projekcie napisania niniejszej książki. Dziękuję Ryanowi Dahlowi za zapoczątkowanie pracy nad platformą Node.js, a także Isaacowi Schlueterowi za kilkuletnie doskonałe kierowanie projektem Node. Dziękuję także rodzinie, przyjaciołom oraz mojej dziewczynie za cierpliwe znoszenie wszystkich bezsennych nocy oraz szerokiego spektrum wyrażanych przeze mnie emocji podczas całego okresu powstawania książki. Oczywiście ogromne podziękowania składam moim rodzicom, którzy przez lata wspierali mnie w mojej pasji komputerowej. Gdyby nie oni, nie byłbym dzisiaj w tym miejscu, w którym jestem. 1 MEAP (ang. Manning Early Access Program) to wprowadzony przez wydawnictwo Manning program, który umożliwia czytelnikom lekturę książki już na etapie jej powstawania — przyp. tłum.

O książce Podstawowym celem książki Node.js w akcji jest pokazanie, jak tworzyć i wdrażać aplikacje Node, ze szczególnym naciskiem na aplikacje sieciowe. Znaczna część książki została poświęcona frameworkom Express i Connect, ponieważ są one niezwykle użyteczne i powszechnie stosowane przez społeczność. Dzięki lekturze książki dowiesz się, jak przygotowywać zautomatyzowane testy, a także jak wdrażać opracowane aplikacje. Książka jest skierowana do doświadczonych programistów sieciowych, którzy są zainteresowani tworzeniem za pomocą Node.js aplikacji skalowalnych i żywo reagujących na działania użytkowników. Ponieważ aplikacje Node.js są tworzone w JavaScript, znajomość wymienionego języka jest niezbędna. Doświadczenie w pracy z powłoką systemu Windows, OS X lub Linux również jest mile widziane.

Mapa drogowa Książka została podzielona na trzy części. W części pierwszej znalazło się wprowadzenie do Node.js oraz prezentacja podstawowych technik, które należy opanować. W rozdziale 1. omówiono cechy charakterystyczne platformy Node, a także pokazano przykładowy kod programu w Node. Rozdział 2. prowadzi czytelnika przez proces tworzenia przykładowej aplikacji. Z kolei w rozdziale 3. zaprezentowano wyzwania, jakie stają przed programistami Node.js, techniki pozwalające na ich przezwyciężenie oraz sposoby pomagające w organizacji kodu aplikacji. Część druga jest największą w książce i koncentruje się na programowaniu aplikacji sieciowych. W rozdziale 4. przedstawiono podstawy tworzenia aplikacji sieciowych opartych na Node, natomiast w rozdziale 5. dowiesz się, jak można przechowywać dane w aplikacji Node. Część druga książki jest też poświęcona frameworkom sieciowym. W rozdziale 6. poznasz framework Connect, dowiesz się, jak działa i jakie korzyści można osiągnąć dzięki jego użyciu. Rozdział 7. pokazuje, jak wbudowane komponenty frameworka Connect można wykorzystać w celu wzbogacenia tworzonych aplikacji sieciowych. Rozdział 8. stanowi wprowadzenie do frameworka Express, natomiast rozdział 9. prezentuje czytelnikowi zaawansowane sposoby użycia frameworka Express. Po przedstawieniu podstaw dotyczących programowania sieciowego część druga książki kończy się objaśnieniem dwóch tematów pokrewnych. Rozdział 10. został poświęcony różnym frameworkom testowym dla Node. Z kolei w

rozdziale 11. dowiesz się, jakie silniki szablonów można stosować w aplikacjach sieciowych Node, aby oddzielić warstwę prezentacyjną od logiki i danych. W części trzeciej książki wykroczymy poza programowanie sieciowe i zobaczysz, do jakich jeszcze innych celów można wykorzystać Node. W rozdziale 12. dowiesz się, jak aplikacje Node wdrażać w serwerach produkcyjnych, jak zapewnić nieustanne działanie aplikacji, a także jak zmaksymalizować wydajność jej działania. W rozdziale 13. poruszono temat tworzenia aplikacji innych niż HTTP, użycie biblioteki Socket.io do opracowywania aplikacji działających w czasie rzeczywistym oraz wykorzystanie użytecznego, wbudowanego API Node. Rozdział 14., ostatni w książce, został poświęcony społeczności Node, a także tworzeniu i publikowaniu modułów Node za pomocą Node Package Manager.

Konwencje zastosowane w kodzie i materiały do pobrania W kodzie przedstawionym w książce zastosowano wiele powszechnie znanych i używanych konwencji programowania w języku JavaScript. Wcięcia zostały uzyskane za pomocą spacji, a nie tabulatorów. Unikaliśmy także wierszy dłuższych niż 80 znaków. W wielu listingach kod został opisany, aby przedstawić dodatkowe objaśnienie kluczowych koncepcji. W przypadku prostych konstrukcji wiersz kodu zawiera pojedyncze polecenie zakończone średnikiem. W blokach kodu, gdzie jedno lub więcej poleceń ujęto w nawiasy klamrowe, lewy nawias klamrowy jest umieszczany na końcu wiersza otwierającego dany blok kodu. Natomiast prawy nawias klamrowy został wcięty w taki sposób, aby pionowo był wyrównany do nawiasu otwierającego blok. Kod źródłowy przykładów przedstawionych w książce można pobrać ze strony ftp://ftp.helion.pl/przyklady/nodejs.zip.

Forum Author Online Zakup książki bezpośrednio w wydawnictwie Manning Publishing daje bezpłatny dostęp do prywatnego forum, w którym można umieszczać komentarze dotyczące książki, zadawać pytania techniczne oraz otrzymać pomoc ze strony autorów i innych czytelników. Aby uzyskać dostęp do forum, przejdź na stronę http://www.manning.com/Node.jsinAction, na której znajdziesz informacje, jak dostać się na forum po rejestracji, jaka pomoc jest oferowana oraz jakie są reguły rządzące forum. Celem

wydawnictwa

Manning

jest

zapewnienie

czytelnikom

możliwości

wymieniania opinii między sobą, a także kontaktu z autorami. Nie oznacza to jednak żadnego zobowiązania ze strony autorów, którzy na forum Author Online udzielają się dobrowolnie i bezpłatnie. Sugerujemy więc zadawanie autorom trudnych pytań, aby wzbudzić ich zainteresowanie! Forum Author Online i archiwa wcześniejszych dyskusji pozostają na witrynie wydawnictwa Manning Publishing tak długo, jak długo oferowana jest dana książka.

Część I Podstawy Node Podczas nauki języka programowania lub poznawania frameworka bardzo często spotykasz nowe koncepcje, które wymagają od Ciebie spojrzenia na pewne rzeczy z innej perspektywy. Node nie jest tutaj wyjątkiem, ponieważ stosuje nowe podejście w wielu aspektach procesu tworzenia aplikacji. W części pierwszej książki dowiesz się, czym dokładnie Node odróżnia się od innych platform, a także poznasz podstawy pracy z Node. Wyjaśnimy, jak wyglądają aplikacje utworzone za pomocą Node, w jaki sposób są zorganizowanie oraz jak radzić sobie z wyzwaniami charakterystycznymi dla Node. Materiał przedstawiony w części pierwszej książki zapewni Ci podstawy niezbędne do tworzenia aplikacji sieciowych w Node, co zostanie dokładnie omówione w części drugiej, a także do tworzenia aplikacji innych niż sieciowe, czym zajmiemy się w części trzeciej.

Rozdział 1. Witamy w Node.js W tym rozdziale: • • • • •

Czym jest Node.js? JavaScript w serwerze. Asynchroniczna i oparta na zdarzeniach natura Node. Rodzaje aplikacji, dla których zaprojektowano Node. Przykładowe programy Node.

Czym jest Node.js? Prawdopodobnie słyszałeś już tę nazwę, a nawet możliwe, że już używasz Node. A może po prostu jesteś tylko ciekawy i chcesz dowiedzieć się czegoś o Node. Obecnie Node jest bardzo popularnym i stosunkowo młodym (zadebiutował w roku 2009) projektem. Szczerze mówiąc, to drugi pod względem ilości obserwatorów projekt w serwisie GitHub (https://github.com/joyent/node), ma sporą grupę fanów w grupie Google (https://groups.google.com/forum/#!forum/nodejs), poświęcony mu kanał IRC (http://webchat.freenode.net/?channels=node.js) oraz obecnie ponad 70 000 modułów opracowanych przez społeczność i dostępnych za pomocą menedżera npm (https://www.npmjs.org/). Jak możesz się przekonać, jest to całkiem popularna platforma. Ryan Dahl o Node. Na stronie http://jsconf.eu/2009/video_nodejs_by_ryan_dahl.html poświęconej konferencji JSCONF 2009, która odbyła się w Berlinie, możesz obejrzeć pierwszą prezentację Node przygotowaną przez Ryana Dahla. Oficjalna witryna internetowa (http://www.nodejs.org/) definiuje Node jako „platformę utworzoną na podstawie środowiska uruchomieniowego JavaScript przeglądarki internetowej Chrome, przeznaczoną do łatwego tworzenia szybkich, skalowalnych aplikacji sieciowych. Node.js używa opartego na zdarzeniach, nieblokującego modelu wejścia--wyjścia, co zapewnia lekkość i efektywność. Stanowi doskonałe rozwiązanie dla działających w czasie rzeczywistym aplikacji intensywnie korzystających z danych oraz aplikacji rozproszonych w różnych urządzeniach”. W tym rozdziale zostaną omówione wymienione poniżej kwestie: Dlaczego JavaScript ma znaczenie podczas tworzenia aplikacji działających po stronie serwera? W jaki sposób przeglądarka internetowa obsługuje operacje wejściawyjścia za pomocą JavaScript? Jak Node obsługuje operacje wejścia-wyjścia w serwerze? Co oznacza wyrażenie „aplikacje DIRT” i dlaczego są one

dobrodziejstwem dla Node? Przykłady kilku podstawowych programów w Node. Naszą uwagę w pierwszej kolejności kierujemy na JavaScript.

1.1. Node jest zbudowane w oparciu o JavaScript Tak czy inaczej JavaScript to obecnie najpopularniejszy na świecie język programowania1. Jeżeli kiedykolwiek zajmowałeś się programowaniem sieciowym, na pewno miałeś styczność z językiem JavaScript. W sieci o ogromnych rozmiarach JavaScript spełnił sen „utwórz raz, uruchamiaj gdziekolwiek”, który twórcy języka Java mieli w latach dziewięćdziesiątych ubiegłego stulecia. W czasie rewolucji spowodowanej przez rozwój technologii Ajax w roku 2005 język JavaScript przestał być postrzegany jako „zabawka” i zaczął być używany przez programistów do tworzenia rzeczywistych i ważnych aplikacji. W tamtym czasie jednymi z ważniejszych aplikacji utworzonych w JavaScript były Google Maps i Gmail, natomiast obecnie mamy wiele aplikacji sieciowych JavaScript, począwszy od Twittera, przez Facebook, aż po GitHub. Od chwili wydania przeglądarki Google Chrome w roku 2008 nieustannie i bardzo szybko poprawia się wydajność działania JavaScript, co jest wynikiem ogromnej konkurencji między producentami poszczególnych przeglądarek internetowych (Mozilla, Microsoft, Apple, Opera i Google). Wydajność nowoczesnych maszyn wirtualnych JavaScript dosłownie zmieniła rodzaje aplikacji, jakie można tworzyć dla sieci2. Fascynującym i, szczerze mówiąc, fantastycznym przykładem jest jslinux3, czyli utworzony w JavaScript emulator pozwalający na wczytanie jądra systemu Linux, pracę w powłoce i kompilację programu w języku C — to wszystko w przeglądarce internetowej. Do programowania po stronie serwera Node używa V8, czyli maszyny wirtualnej JavaScript stosowanej w przeglądarce Google Chrome. Silnik V8 zapewnia Node ogromną wydajność, ponieważ eliminuje wszelkich pośredników oraz preferuje kompilację w rodzimym kodzie maszynowym, zamiast wykonywać kod bajtowy za pomocą interpretera. Skoro Node używa języka JavaScript po stronie serwera, wiążą się z tym dodatkowe korzyści: Programiści mogą tworzyć aplikacje sieciowe w jednym języku, co pomaga w redukcji operacji przełączania kontekstu między programowaniem po stronie klienta i serwera, a także pozwala na współdzielenie kodu między klientem i serwerem. Dzięki temu ten sam kod można stosować na przykład do wersyfikacji formularzy lub w logice

gry. JSON to obecnie bardzo popularny format wymiany danych, a ponadto jest rodzimym formatem języka JavaScript. JavaScript to język używany w różnych bazach danych typu NoSQL (takich jak CouchDB i MongoDB), a więc praca z nimi nie nastręcza trudności (na przykład język powłoki i zapytań MongoDB to JavaScript, natomiast funkcjonalność Map/Reduce w CouchDB również opiera się na JavaScript). JavaScript to wynik kompilacji w wielu różnych językach programowania4. Node używa jednej maszyny wirtualnej (V8) zgodnej ze standardem ECMAScript5. Innymi słowy, z użyciem nowych funkcji języka JavaScript w Node nie musisz czekać do chwili, aż we wszystkich przeglądarkach internetowych zostanie wprowadzona ich obsługa. Kto wie, być może JavaScript stanie się fantastycznym językiem przeznaczonym do tworzenia aplikacji działających po stronie serwera. Na razie dzięki dużemu zasięgowi sieci, wydajności oraz innym wspomnianym wcześniej cechom charakterystycznym Node zyskało ogromną popularność. Jednak JavaScript to tylko jeden element układanki; sposób, w jaki Node używa języka JavaScript, jest jeszcze bardziej fascynujący. Aby zrozumieć środowisko Node, konieczne jest zagłębienie się w najlepiej Ci znane środowisko JavaScript, jakim bez wątpienia jest przeglądarka internetowa.

1.2. Asynchroniczna i oparta na zdarzeniach: przeglądarka internetowa Node zapewnia asynchroniczną i opartą na zdarzeniach platformę dla kodu JavaScript działającego po stronie serwera. Język JavaScript jest stosowany w serwerze praktycznie w taki sam sposób, w jaki przeglądarka internetowa dostarcza JavaScript klientowi. W celu poznania sposobu działania Node konieczne jest poznanie sposobu działania przeglądarki internetowej. I jedno, i drugie działa w oparciu o zdarzenia (używają pętli zdarzeń) oraz nieblokujące operacje wejścia-wyjścia (używają asynchronicznych operacji wejścia-wyjścia). Wyjaśnienie zostanie przedstawione na przykładzie. Pętla zdarzeń i asynchroniczne operacje wejścia-wyjścia. Więcej informacji na temat pętli zdarzeń oraz asynchronicznych operacji wejścia-

wyjścia znajdziesz w odpowiednich http://en.wikipedia.org/wiki/Event_loop http://en.wikipedia.org/wiki/Asynchronous_I/O.

artykułach

Wikipedii: i

Spójrz na poniższy fragment kodu, w którym biblioteka jQuery wykonuje żądanie Ajax za pomocą obiektu XMLHttpRequest (XHR): $.post('/resource.json', function (data) { Operacja wejścia-wyjścia nie wykonywania bloku kodu.

wstrzymuje

console.log(data); }); // Kontynuacja wykonywania skryptu.

Powyższy fragment kodu powoduje wykonanie żądania HTTP w celu pobrania zasobu o nazwie resource.json. Po otrzymaniu odpowiedzi następuje wywołanie funkcji anonimowej (w tym kontekście to jest „wywołanie zwrotne”) zawierające argument data, czyli dane uzyskane z wymienionego żądania. Zwróć uwagę na fakt, że kod nie został utworzony w następujący sposób: var data = $.post('/resource.json'); Operacja wejścia-wyjścia wstrzymuje wykonanie bloku kodu. console.log(data);

W kodzie utworzonym w powyższy sposób przyjęto założenie, że odpowiedź dla zasobu resource.json zostanie umieszczona w zmiennej data, gdy tylko będzie dostępna, a wywołanie funkcji console.log() nie nastąpi do chwili otrzymania wspomnianych danych. Operacja wejścia-wyjścia (żądanie Ajax) będzie „blokowała” kontynuację wykonywania skryptu, dopóki nie zostaną otrzymane dane. Ponieważ przeglądarka internetowa jest aplikacją jednowątkową, to jeśli wykonanie żądania zajmie 400 ms, wówczas wszelkie inne zdarzenia na stronie będą wstrzymane aż do chwili wznowienia działania skryptu. Możesz sobie wyobrazić, jakie wrażenie odniesie użytkownik, jeżeli odtwarzanie animacji zostanie wstrzymane, gdy spróbuje on w inny sposób korzystać ze strony. Na szczęście w omawianym przykładzie tak się nie dzieje. Kiedy operacja wejścia-wyjścia jest przeprowadzana w przeglądarce internetowej, odbywa się poza pętlą zdarzeń (czyli poza głównym procesem wykonywania skryptu), a po zakończeniu operacji emitowane jest zdarzenie6. Jak pokazano na rysunku 1.1, wspomniane zdarzenie jest obsługiwane przez funkcję (często nazywaną „wywołaniem zwrotnym”). Operacja wejścia-wyjścia jest przeprowadzana asynchronicznie, nie „blokuje” wykonywania skryptu i pozwala pętli zdarzeń reagować na wszelkie inne działania lub żądania przeprowadzane na stronie. Dzięki temu przeglądarka internetowa będzie reagowała na działania klienta i zapewni możliwość zachowania interaktywności na danej stronie.

Rysunek 1.1. Przykład nieblokującej operacji wejścia-wyjścia w przeglądarce internetowej

Zapamiętaj powyższe informacje i przechodzimy do serwera.

1.3. Asynchroniczny i oparty na zdarzeniach: serwer W większości przypadków prawdopodobnie znasz stosowany w programowaniu po stronie serwera konwencjonalny model operacji wejścia-wyjścia, na przykład przedstawiony w podrozdziale 1.2 „blokujący” przykład użycia biblioteki jQuery. Poniżej pokazano, jak tego rodzaju rozwiązanie wygląda w PHP: $result = mysql_query('SELECT * FROM myTable'); Wykonywanie kodu jest wstrzymane aż do zakończenia zapytania bazy danych. print_r($result);

Powyższy fragment kodu wykonuje pewne operacje wejścia-wyjścia, a przetwarzanie jest wstrzymane aż do chwili otrzymania wszystkich danych. W wielu aplikacjach tego rodzaju model sprawdza się doskonale i łatwo go zastosować. Być może nie jest to zupełnie oczywiste, ale proces ma swój stan (czyli pamięć) i do chwili zakończenia operacji wejścia-wyjścia dosłownie nic się nie dzieje. W zależności od wielkości opóźnienia operacji wejścia-wyjścia czas

oczekiwania może wynosić od 10 ms do nawet kilku minut. Opóźnienie może mieć wiele niespodziewanych przyczyn: Dysk przeprowadza operację konserwacyjną, co spowalnia lub wstrzymuje inne operacje odczytu/zapisu. Z powodu większego obciążenia zapytanie do bazy danych jest wykonywane wolniej. Z jakiegoś powodu pobranie zasobu z danej witryny przebiega dzisiaj bardzo wolno. Jeżeli działanie programu zostanie wstrzymane do chwili zakończenia operacji wejścia--wyjścia, to co zrobi serwer, gdy pojawią się kolejne żądania do obsłużenia? W takim kontekście najczęściej jest stosowane podejście wielowątkowe. Najczęstszy przykład implementacji to użycie po jednym wątku dla poszczególnych połączeń i zdefiniowanie puli wątków dla wspomnianych połączeń. Wątki możesz potraktować jako obliczeniowe przestrzenie robocze, w których procesor wykonuje po jednym zadaniu. W wielu przypadkach wątek znajduje się w ramach procesu i zawiera własną pamięć roboczą. Każdy wątek obsługuje jedno lub więcej połączeń serwera. Wprawdzie wydaje się to naturalnym rozwiązaniem podczas implementacji serwera — przynajmniej dla programistów zajmujących się tym od dłuższego czasu — ale warto pamiętać, że zarządzanie wątkami w aplikacji może być trudnym zadaniem. Ponadto gdy ogromna liczba wątków jest wymagana do obsługi wielu jednoczesnych połączeń serwera, oznacza to wykorzystanie dużej ilości zasobów systemu operacyjnego. Wątki wymagają od procesora przeprowadzania operacji przełączania kontekstu, a także zużywają pamięć RAM. Aby to zilustrować, posłużymy się testem wydajności (pokazany na rysunku 1.2, pochodzi ze strony http://blog.webfaction.com/2008/12/a-little-holiday-present10000-reqssec-with-nginx-2/) porównującym serwery NGINX i Apache. NGINX (http://nginx.com/) to podobnie jak Apache serwer WWW, ale zamiast stosować podejście wielowątkowe z blokującymi operacjami wejścia-wyjścia, używa pętli zdarzeń wraz z asynchronicznymi operacjami wejścia-wyjścia (podobnie jak w przypadku przeglądarki internetowej i Node). Z powodu wybranego podejścia projektowego serwer NGINX bardzo często potrafi obsłużyć więcej żądań i połączonych klientów, co czyni z niego lepsze rozwiązanie w zakresie reakcji na działania użytkowników7. W Node operacje wejścia-wyjścia są prawie zawsze przeprowadzane poza główną pętlą zdarzeń, co pozwala serwerowi na efektywne działanie i reakcję na operacje wykonywane przez użytkownika, podobnie jak ma to miejsce w serwerze NGINX. Dlatego też znacznie rzadziej mamy do czynienia z procesem powiązanym z operacją wejścia-wyjścia, ponieważ opóźnienie operacji wejścia-

wyjścia nie powoduje awarii serwera lub użycia zasobów jak w przypadku blokowania. W efekcie obciążenie systemu przez serwer jest mniejsze8. Połączenie modeli asynchronicznego i opartego na zdarzeniach, a także powszechnie dostępnego języka JavaScript pomaga w otwarciu ekscytującego świata działających w czasie rzeczywistym aplikacji, które intensywnie korzystają z danych.

Rysunek 1.2. Test wydajności porównujący serwery Apache i NGINX

1.4. Aplikacje DIRT Wobec aplikacji opracowanych na platformie Node stosowany jest akronim DIRT (ang. Data-Intensive Real-Time). Ponieważ Node nie powoduje dużego obciążenia podczas przetwarzania operacji wejścia-wyjścia, doskonale sprawdza się w trakcie stosowania proxy lub mieszania danych między poszczególnymi strumieniami. Serwer ma możliwość zachowania wielu otwartych połączeń podczas obsługi dużej liczby żądań i jednocześnie nie zużywa zbyt wiele pamięci. Podobnie jak w przypadku przeglądarki internetowej takie rozwiązanie zostało zaprojektowane w celu efektywnego reagowania na działania użytkownika.

Aplikacje działające w czasie rzeczywistym są nowością w sieci. Obecnie wiele aplikacji sieciowych dostarcza informacje niemal natychmiast, implementując funkcje takie jak jednoczesna współpraca użytkowników, wskazywanie w czasie rzeczywistym położenia pojazdów, a także gry dla wielu użytkowników. Niezależnie od tego, czy istniejące aplikacje są wzbogacane o komponenty działające w czasie rzeczywistym, czy powstają zupełnie nowe rodzaje aplikacji, sieć ewoluuje w kierunku coraz większej reakcji na działania użytkownika oraz środowisk zapewniających możliwość współpracy poszczególnych użytkowników. Jednak wspomniane nowe rodzaje aplikacji aż „proszą się” o platformę pozwalającą na niemal natychmiastowe udzielanie odpowiedzi dużej liczbie jednocześnie działających użytkowników. Node doskonale się tutaj sprawdza, nie tylko w przypadku aplikacji sieciowych, ale również innych wykonujących ogromną ilość operacji wejścia-wyjścia. Dobrym przykładem aplikacji typu DIRT utworzonej za pomocą Node jest Browseling (pokazana na rysunku 1.3 witryna https://browserling.com/). Wymieniona aplikacja pozwala na użycie w przeglądarce internetowej silników innych przeglądarek internetowych. To niezwykle użyteczne rozwiązanie dla projektantów witryn internetowych, ponieważ pozwala im uniknąć konieczności instalacji wielu różnych przeglądarek internetowych i systemów operacyjnych tylko w celu testowania tworzonych witryn. Aplikacja Browseling wykorzystuje oparty na Node projekt o nazwie StackVM, który zarządza maszynami wirtualnymi (VM) utworzonymi za pomocą emulatora QEMU (Quick Emulator). Wspomniany QEMU emuluje procesor i wszelkie komponenty niezbędne do uruchomienia przeglądarki internetowej.

Rysunek 1.3. Utworzona w oparciu o Node aplikacja Browseling pozwala na interaktywne testowanie witryny internetowej w wielu różnych przeglądarkach internetowych

Aplikacja Browseling korzysta z maszyn wirtualnych do uruchamiania przeglądarek internetowych. Wprowadzone w przeglądarce internetowej użytkownika dane wejściowe pochodzące z klawiatury i myszy są przekazywane do emulowanej przeglądarki, która z kolei wygenerowane obszary strumieniuje do przeglądarki internetowej użytkownika, gdzie są wyświetlane. Mechanizm zastosowany w aplikacji Browseling pokazano na rysunku 1.4. Aplikację Browseling uzupełnia inny projekt oparty na Node, o nazwie Testling (https://ci.testling.com/), który pozwala na uruchomienie z poziomu wiersza poleceń zestawu testów w jednocześnie wielu przeglądarkach internetowych.

Rysunek 1.4. Mechanizm działania aplikacji Browseling

Browseling i Testling to dobre przykłady aplikacji DIRT, a infrastruktura wymagana do implementacji skalowanych aplikacji sieciowych takich jak wymienione będzie miała ważne znaczenie, gdy rozpoczniesz tworzenie swojej pierwszej aplikacji Node. Zapoznaj się teraz z narzędziami standardowo oferowanymi przez API Node.

1.5. Domyślna aplikacja jest typu DIRT Node od podstaw budowano z założeniem, że będzie stosowany model asynchroniczny i oparty na zdarzeniach. Język JavaScript nigdy nie posiadał standardowych bibliotek wejścia-wyjścia, które są powszechnie spotykane w innych językach służących do tworzenia aplikacji działających po stronie serwera. Środowisko „hosta” zawsze zajmowało się tym dla JavaScript. Najczęściej stosowanym środowiskiem hosta dla JavaScript — to znaczy tym, do którego programiści najbardziej się przyzwyczaili — jest przeglądarka internetowa, która z założenia jest asynchroniczna i oparta na zdarzeniach. Node próbuje zachować spójność między przeglądarką internetową i serwerem, co odbywa się przez implementację wielu najczęściej stosowanych obiektów hosta, czyli między innymi: Timer API (na przykład setTimeout()), Console API (na przykład console.log()).

Node zawiera także podstawowy zestaw modułów dla wielu rodzajów sieciowych i plikowych operacji wejścia-wyjścia. Obejmuje to między innymi moduły dla HTTP, TLS, HTTPS, systemu plików (POSIX), Datagram (UDP) i NET (TCP). Podstawowy zestaw jest celowo mały, działa na niskim poziomie i pozostaje nieskomplikowany; oferuje jedynie „klocki” pozwalające na tworzenie aplikacji opartych na operacjach wejścia--wyjścia. Przygotowane przez firmy trzecie moduły zapewniają znacznie lepsze abstrakcje przeznaczone do rozwiązywania najczęściej spotykanych problemów. Mając to wszystko na uwadze, być może zastanawiasz się, jak wygląda kod Node. Zapoznaj się teraz z kilkoma prostymi przykładami, takimi jak: Platforma kontra framework Node to platforma dla aplikacji JavaScript i nie należy jej mylić z tak zwanym frameworkiem. Bardzo łatwo uznać Node za coś w rodzaju Rails lub Django dla JavaScript, gdy tak naprawdę Node działa na o wiele niższym poziomie. Jeżeli jesteś zainteresowany frameworkami dla aplikacji sieciowych, warto tutaj wspomnieć, że w dalszej części książki poznasz Express, czyli popularny framework dla Node.

Prosty przykład aplikacji asynchronicznej. Serwer HTTP, czyli aplikacja typu „Witaj, świecie”. Przykład strumieni. W pierwszej kolejności zajmiemy się prostą aplikacją asynchroniczną.

1.5.1. Prosty przykład aplikacji asynchronicznej W podrozdziale 1.2 pokazano poniższy przykład żądania Ajax wykonany za pomocą biblioteki jQuery: $.post('/resource.json', function (data) { console.log(data); });

Zrobimy teraz coś podobnego w Node, ale wykorzystamy moduł systemu plików (fs) do wczytania zasobu resource.json z dysku. Zwróć uwagę na podobieństwo między dwoma pokazanymi programami: var fs = require('fs'); fs.readFile('./resource.json', function (er, data) { console.log(data); })

W powyższym programie zasób w postaci pliku resource.json jest wczytywany z dysku. Po wczytaniu wszystkich danych nastąpi wywołanie funkcji anonimowej

(wywołanie zwrotne) zawierającej argumenty er (jeżeli wystąpi jakikolwiek błąd) i data (zawiera dane pliku). Proces jest przeprowadzany w tle, co zapewnia możliwość obsługi wszelkich innych operacji, które mogą wystąpić, zanim zostaną wczytane wszystkie dane. W tym przypadku automatycznie wykorzystywane są wszystkie omówione wcześniej korzyści wynikające z asynchroniczności i podejścia opartego na zdarzeniach. Różnica polega na tym, że zamiast z poziomu przeglądarki internetowej wykonywać żądanie Ajax za pomocą biblioteki jQuery, następuje użycie platformy Node w celu uzyskania dostępu do systemu plików i pobrania resource.json. Ten mechanizm pokazano na rysunku 1.5.

1.5.2. Serwer HTTP Platforma Node jest bardzo często używana do tworzenia serwerów. Node ułatwia opracowywanie różnego rodzaju serwerów. Wspomniane zastosowanie może wydawać się dziwne, jeśli przyzwyczaiłeś się do używania serwera hosta dla tworzonych aplikacji (przykładem może być aplikacja PHP uruchamiana w serwerze Apache hosta). W Node serwer i aplikacja stanowią jedno i to samo.

Rysunek 1.5. Przykład nieblokującej operacji wejścia-wyjścia wykonywanej przez Node

Poniżej przedstawiono przykład serwera HTTP, który na każde żądanie odpowiada po prostu komunikatem Witaj, świecie: var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Witaj, świecie\n'); }).listen(3000); console.log('Serwer jest dostępny pod adresem http://localhost:3000/');

Po otrzymaniu dowolnego żądania następuje wywołanie funkcji function(req, res) i udzielenie odpowiedzi w postaci komunikatu o treści Witaj, świecie. Ten model oparty na zdarzeniach jest podobny do nasłuchiwania zdarzenia onclick w przeglądarce internetowej. Kliknięcie może wystąpić w dowolnej chwili, więc przygotowujesz funkcję odpowiedzialną za jego obsługę. W omawianym przykładzie Node zapewnia funkcję wywoływaną po otrzymaniu każdego żądania. Poniżej przedstawiono inny sposób osiągnięcia tego samego celu, ale tym razem zdarzenie request zostało zdefiniowane znacznie czytelniej: var http = require('http'); var server = http.createServer(); server.on('request', function (req, res) { Konfiguracja nasłuchiwania zdarzeń dla żądania. res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Witaj, świecie\n'); }) server.listen(3000); console.log('Serwer jest dostępny pod adresem http://localhost:3000/');

1.5.3. Strumieniowanie danych Node oferuje również możliwości w zakresie obsługi strumieni i strumieniowania. Strumień możesz potraktować jak tablicę, ale dane zamiast być umieszczone na pewnej powierzchni, są dostarczane w pewnym zakresie czasu. Dzięki zbieraniu danych fragment po fragmencie programista zyskuje możliwość ich obsługi natychmiast po otrzymaniu, zamiast oczekiwać z działaniem aż do chwili otrzymania wszystkich danych. Poniżej przedstawiono sposób strumieniowania danych zasobu resource.json: var stream = fs.createReadStream('./resource.json') stream.on('data', function (chunk) { Zdarzenie danych jest wyzwalane po otrzymaniu

każdego fragmentu danych. console.log(chunk) }) stream.on('end', function () { console.log('zakończono') })

Zdarzenie data jest wywoływane po otrzymaniu nowego fragmentu danych, natomiast zdarzenie end po otrzymaniu wszystkich fragmentów. W zależności od rodzaju danych fragmenty mogą mieć różną wielkość. Ten rodzaj dostępu na niskim poziomie i odczyt strumienia pozwala na efektywną obsługę otrzymywanych danych zamiast oczekiwania na ich pełne buforowanie w pamięci. Platforma Node umożliwia również obsługę zapisywalnych strumieni, co pozwala na zapis poszczególnych fragmentów danych. Jednym z dostępnym rozwiązań w tym zakresie jest obiekt odpowiedzi (res), gdy żądanie pojawia się w serwerze HTTP. Odczytywane i zapisywane strumienie mogą być łączone i tworzyć w ten sposób potoki; to przypomina użycie operatora | w powłoce. W ten sposób zyskujesz możliwość efektywnego zapisu danych po ich otrzymaniu — bez konieczności oczekiwania na odczyt wszystkich danych zasobu i dopiero wtedy ich zapisanie. Poniżej przedstawiono klientowi:

przykład

serwera

HTTP

strumieniującego

obraz

var http = require('http'); var fs = require('fs'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'image/png'}); Potokowanie danych ze strumienia odczytywanego do zapisywanego. fs.createReadStream('./image.png').pipe(res); }).listen(3000); console.log('Serwer jest dostępny pod adresem http://localhost:3000/');

W powyższym fragmencie kodu dane są odczytywane z pliku (fs.createReadStream()) i wysyłane (pipe()) do klienta (res), gdy tylko będą dostępne. Podczas strumieniowania danych pętla zdarzeń zachowuje możliwość obsługi innych zdarzeń. Node domyślnie zapewnia podejście DIRT na wielu platformach, między innymi UNIX i Windows. Wykorzystywana biblioteka wejścia-wyjścia (libuv) została specjalnie opracowana tak, aby zapewnić ten sam sposób działania niezależnie od używanego systemu operacyjnego. Takie rozwiązanie znacznie ułatwia przenoszenie aplikacji między różnymi urządzeniami i uruchamianie ich w wielu

urządzeniach, jeśli zajdzie potrzeba.

1.6. Podsumowanie Podobnie jak każda inna technologia, także platforma Node nie jest panaceum na wszystko. Pomaga jednak w rozwiązaniu pewnych problemów, a także otwiera nowe możliwości. Jedną z interesujących funkcji Node jest to, że łączy osoby zajmujące się poprzednio programowaniem w odmiennych aspektach systemu. Wielu rozpoczynających pracę z Node było wcześniej programistami aplikacji JavaScript działających po stronie klienta, inni tworzyli programy działające po stronie serwera, a jeszcze inni tworzyli oprogramowanie systemowe. Mam nadzieję, że niezależnie od wcześniejszego doświadczenia w programowaniu już wiesz, jak możesz wykorzystać Node w swojej pracy. Podsumowując, platforma Node: została zbudowana w oparciu o język JavaScript; jest asynchroniczna i oparta na zdarzeniach; została zaprojektowana dla aplikacji działających w czasie rzeczywistym i korzystających z ogromnej ilości danych. W rozdziale 2. utworzymy prostą aplikację sieciową typu DIRT. Zobaczysz, jak działa aplikacja zbudowana na platformie Node. 1 Zobacz wideo zatytułowane „Javascript: Your New Overlord” w serwisie YouTube http://www.youtube.com/watch?v=Trurfqh_6fQ. 2 Przykłady znajdziesz na stronie „Chrome Experiments” w witrynie http://www.chromeexperiments.com/. 3 Jslinux, utworzony w JavaScript emulator komputera PC z systemem Linux http://bellard.org/jslinux/. 4 Lista języków kompilowanych do JavaScript znajduje się na stronie https://github.com/jashkenas/coffee-script/wiki/List-of-languages-that-compileto-JS. 5 Więcej informacji o standardzie ECMAScript znajdziesz w artykule Wikipedii: http://pl.wikipedia.org/wiki/ECMAScript. 6 Warto pamiętać o istnieniu kilku wyjątków blokujących wykonywanie skryptu przez przeglądarkę internetową, choć ich użycie nie jest zalecane. Są to: komunikat (alert), pytanie, potwierdzenie i synchroniczne żądanie XHR. 7 Jeżeli chcesz dowiedzieć się więcej na ten temat, zapoznaj się z artykułem

dotyczącym „problemu C10K”, który znajdziesz na stronie http://www.kegel.com/c10k.html. 8 Więcej informacji na ten temat znajdziesz na stronie http://nodejs.org/about/.

Rozdział 2. Tworzenie aplikacji wielopokojowego czatu W tym rozdziale: • Pierwsze spojrzenie na różne komponenty Node. • Przykład aplikacji Node działającej w czasie rzeczywistym. • Współpraca między klientem i serwerem.

W rozdziale 1. dowiedziałeś się, jak programowanie asynchroniczne z użyciem Node różni się od konwencjonalnego programowania synchronicznego. W tym rozdziale wykorzystamy platformę Node w praktyce do utworzenia małej, opartej na zdarzeniach aplikacji czatu. Nie przejmuj się, jeśli nie zrozumiesz całego materiału przedstawionego w rozdziale. Naszym celem jest objaśnienie sposobu programowania z użyciem Node i jedynie zaprezentowanie możliwości, jakie będziesz mieć po zakończeniu lektury niniejszej książki. W rozdziale przyjęto założenie, że masz doświadczenie w programowaniu aplikacji sieciowych, a także podstawową wiedzę z zakresu HTTP i biblioteki jQuery. W trakcie lektury materiału przedstawionego w tym rozdziale: poznasz tworzoną aplikację i zobaczysz, jak działa; poznasz technologie wymagane do jej utworzenia i przeprowadzisz początkową konfigurację aplikacji; przygotujesz dla aplikacji kod HTML, CSS i JavaScript działający po stronie klienta; zajmiesz się obsługą wiadomości czatu za pomocą biblioteki Socket.IO; za pomocą kodu JavaScript działającego po stronie klienta przygotujesz interfejs użytkownika aplikacji. Zaczynamy od ogólnego omówienia aplikacji — dowiesz się, jak aplikacja będzie wyglądać i działać, gdy zakończymy proces jej tworzenia.

2.1. Ogólny opis aplikacji Aplikacja tworzona w tym rozdziale pozwala użytkownikom na prowadzenie między sobą internetowego czatu przez wprowadzanie wiadomości w prostym formularzu, jak pokazano na rysunku 2.1. Wprowadzona w formularzu wiadomość zostaje wysłana wszystkim uczestnikom czatu znajdującym się w tym samym pokoju.

Rysunek 2.1. Wprowadzenie wiadomości w aplikacji czatu

Po uruchomieniu aplikacji użytkownikowi automatycznie będzie przypisana nazwa gościa, którą może zmienić przez wydanie polecenia, jak pokazano na rysunku 2.2. Polecenia czatu są poprzedzane ukośnikiem (/).

Rysunek 2.2. Zmiana nazwy użytkownika czatu

Podobnie użytkownik może wydać polecenie utworzenia nowego pokoju czatu (lub dołączenia do istniejącego), co pokazano na rysunku 2.3. Podczas tworzenia pokoju czatu nazwa nowego pokoju będzie wyświetlana na poziomym pasku znajdującym się na górze aplikacji czatu. Sam pokój zostanie również umieszczony na liście dostępnych pokoi wyświetlanej po prawej stronie obszaru wiadomości.

Rysunek 2.3. Zmiana pokoju czatu

Po przejściu użytkownika do nowego pokoju system potwierdzi tę zmianę, jak pokazano na rysunku 2.4.

Rysunek 2.4. Wynik przejścia do nowego pokoju czatu

Wprawdzie funkcjonalność omawianej tutaj aplikacji bez wątpienia jest bardzo ograniczona, ale jednocześnie prezentuje ona ważne i podstawowe koncepcje wymagane do utworzenia aplikacji sieciowej działającej w czasie rzeczywistym. Omawiania aplikacji pokazuje, jak Node może oferować dane HTTP (na przykład pliki statyczne) i jednocześnie obsługiwać dane w czasie rzeczywistym (wiadomości czatu). Ponadto dzięki omawianej aplikacji możesz się przekonać, jak zorganizowane są aplikacje Node i jak wygląda zarządzanie zależnościami. Teraz przejdziemy do technologii wymaganych do implementacji aplikacji czatu.

2.2. Wymagania aplikacji i konfiguracja początkowa Tworzona w rozdziale aplikacja musi oferować: Udostępnianie plików statycznych (takich jak HTML, CSS i skrypty JavaScript działające po stronie klienta). Obsługę przez serwer wiadomości czatu. Obsługę wiadomości czatu w przeglądarce internetowej użytkownika. Aby udostępniać pliki statyczne, wykorzystamy moduł http wbudowany w Node. Jednak podczas udostępniania plików za pomocą protokołu HTTP zwykle nie wystarczy jedynie wysłać zawartość pliku. Konieczne jest również wskazanie rodzaju wysyłanego pliku. Odbywa się to przez ustawienie nagłówka Content-Type protokołu HTTP i podanie właściwego typu MIME dla pliku. W celu wyszukania wspomnianych typów MIME trzeba będzie użyć modułu o nazwie mime opracowanego przez firmę trzecią. Typy MIME . Dokładne omówienie typów MIME znajdziesz w artykule Wikipedii: http://pl.wikipedia.org/wiki/Multipurpose_Internet_Mail_Extensions. Do obsługi wiadomości związanych z czatem można wykorzystać technologię Ajax. Jednak aby zachować jak największy stopień reakcji aplikacji na działania użytkownika, konieczne jest uniknięcie tradycyjnego rozwiązania Ajax stosowanego do wysyłania wiadomości. Ajax używa protokołu HTTP jako mechanizmu transportowego, a wspomniany HTTP nie został opracowany do prowadzenia komunikacji w czasie rzeczywistym. Kiedy wiadomość jest wysyłana za pomocą HTTP, konieczne jest użycie nowego połączenia TCP/IP. Otwieranie i zamykanie połączeń zabiera cenny czas, a ilość przekazywanych danych rośnie, ponieważ każde żądanie zawiera nagłówki HTTP. Zamiast implementować rozwiązanie oparte na HTTP, w omawianej aplikacji wykorzystamy technologię WebSocket

(http://pl.wikipedia.org/wiki/WebSocket). Została ona zaprojektowana do prowadzenia dwukierunkowej, lekkiej komunikacji w czasie rzeczywistym za pomocą jednego gniazda TCP. Ponieważ w większości przypadków jedynie przeglądarki zgodne z HTML5 obsługują WebSocket, w aplikacji użyjemy popularnej biblioteki Socket.IO (http://socket.io/). Ta biblioteka oferuje wiele rozwiązań awaryjnych, między innymi użycie technologii Flash, gdy zastosowanie WebSocket okaże się niemożliwe. Wspomniane rozwiązania awaryjne są automatycznie obsługiwane przez bibliotekę Socket.IO i nie wymagają dodatkowego kodu lub konfiguracji. Dokładniejsze omówienie biblioteki Socket.IO znajdziesz w rozdziale 13. Zanim faktycznie zajmiemy się pracą i przystąpimy do początkowej konfiguracji struktury plików i zależności aplikacji, warto dowiedzieć się, jak Node pozwala na jednoczesną obsługę HTTP i WebSocket. To jeden z powodów, dla których platforma Node jest doskonałym wyborem w przypadku aplikacji działających w czasie rzeczywistym.

2.2.1. Obsługa HTTP i WebSocket Wprawdzie w omawianej aplikacji unikamy użycia technologii Ajax do wysyłania i otrzymywania wiadomości czatu, ale nadal korzystamy z HTTP do dostarczania plików HTML, CSS i kodu JavaScript działającego po stronie klienta, które powodują przygotowanie aplikacji w przeglądarce internetowej użytkownika. Jak pokazano na rysunku 2.5, Node może bardzo łatwo jednocześnie obsługiwać HTTP i WebSocket za pomocą pojedynczego portu TCP/IP. Standardowo zawiera moduł zapewniający obsługę funkcjonalności HTTP. Dla Node firmy trzecie opracowały również wiele innych modułów, na przykład Express, które zostały zbudowane w oparciu o standardowe funkcje oferowane przez Node i pozwalają na jeszcze łatwiejsze udostępnianie treści. Więcej informacji dotyczących użycia modułu Express podczas tworzenia aplikacji sieciowych znajdziesz w rozdziale 8. W aplikacji tworzonej w tym rozdziale wykorzystamy standardowe możliwości Node.

Rysunek 2.5. Obsługa HTTP i WebSocket w jednej aplikacji

Skoro dowiedziałeś się już, jakie podstawowe technologie będą wykorzystane w budowanej aplikacji, możemy przystąpić do pracy. Czy musisz zainstalować Node? Jeżeli jeszcze nie zainstalowałeś Node, dokładne omówienie instalacji znajdziesz w dodatku A.

2.2.2. Tworzenie struktury plików aplikacji Pierwszym krokiem podczas budowy omawianej aplikacji jest utworzenie katalogu dla projektu. Główny plik aplikacji będzie umieszczony we wspomnianym katalogu. Konieczne jest również dodanie podkatalogu lib, w którym znajdzie się logika działająca po stronie serwera. Ponadto trzeba utworzyć podkatalog public przeznaczony dla plików używanych po stronie klienta. Następnie w podkatalogu public utwórz dwa kolejne: javascripts i stylesheets. Struktura katalogów powinna wyglądać tak, jak pokazano na rysunku 2.6. Warto w tym miejscu dodać, że choć zdecydowaliśmy się na organizację plików w przedstawiony sposób, to jednak Node nie wymaga stosowania żadnej struktury plików. Pliki składające się na aplikację możesz umieścić w najbardziej odpowiadający Ci sposób.

Rysunek 2.6. Struktura katalogu projektu dla aplikacji czatu

Po przygotowaniu struktury katalogów możemy przystąpić do zdefiniowania zależności aplikacji. W omawianym kontekście zależność aplikacji oznacza moduły konieczne do zainstalowania, aby dostarczyć funkcje wymagane przez aplikację. Na przykład przyjmujemy założenie o tworzeniu aplikacji wymagającej dostępu do danych przechowywanych w bazie danych MySQL. Node nie jest standardowo wyposażone w moduł pozwalający na uzyskanie dostępu do MySQL. Konieczna

jest więc instalacja modułu opracowanego przez firmę trzecią i wspomniany moduł jest wówczas zależnością.

2.2.3. Wskazanie zależności Wprawdzie istnieje możliwość utworzenia aplikacji Node bez wskazywania zależności, ale dobrym nawykiem jest poświęcenie chwili czasu na ich zdefiniowanie. W ten sposób konfiguracja aplikacji będzie nieco łatwiejsza, jeśli inny użytkownik będzie chciał używać tej aplikacji lub jeśli planujesz jej uruchamianie w więcej niż tylko jednym miejscu. Zależności aplikacji są definiowane w pliku o nazwie package.json. Wymieniony plik zawsze powinien znajdować się w katalogu głównym aplikacji. Zawartość pliku package.json to wyrażenie JSON w standardzie opisu pakietu CommonJS (http://wiki.commonjs.org/wiki/Packages/1.0) opisujące aplikację. W pliku package.json można podać wiele informacji, ale najważniejsze to nazwa aplikacji, wersja, opis jej działania oraz zależności aplikacji. W listingu 2.1 przedstawiono plik opisujący funkcjonalność i zależności aplikacji tworzonej w tym rozdziale. Plik zapisz pod nazwą package.json w katalogu głównym aplikacji. Listing 2.1. Plik opisujący aplikację { "name": "chatrooms", Nazwa pakietu . "version": "0.0.1", "description": "Minimalistyczny serwer wielopokojowego czatu", "dependencies": { Zależności pakietu. "socket.io": "~0.9.6", "mime": "~1.2.7" } }

Jeżeli zawartość pliku wydaje Ci się nieco dziwna, nie przejmuj się tym teraz. Więcej informacji dotyczących pliku package.json znajdziesz w następnym rozdziale, a jego dokładne omówienie w rozdziale 14.

2.2.4. Instalacja zależności Po zdefiniowaniu pliku package.json instalacja zależności aplikacji staje się niezwykle łatwym zadaniem. Menedżer pakietów Node (https://github.com/npm/npm) jest dostarczany standardowo wraz z Node. Oferuje doskonałe funkcje, między innymi możliwość łatwej instalacji modułów Node opracowanych przez firmy trzecie oraz globalnego udostępniania

modułów opracowanych przez Ciebie. Ponadto za pomocą pojedynczego polecenia potrafi odczytać zależności z pliku package.json, a następnie je zainstalować. Z poziomu katalogu głównego tworzonej aplikacji wydaj poniższe polecenie: npm install

Jeżeli teraz zajrzysz do katalogu aplikacji, przekonasz się, że jest w nim nowy podkatalog o nazwie node_modules, jak pokazano na rysunku 2.7. Wymieniony podkatalog zawiera zależności aplikacji. Mając przygotowaną strukturę katalogu aplikacji i zainstalowane zależności, można już przystąpić do tworzenia logiki aplikacji.

Rysunek 2.7. Po użyciu menedżera npm do instalacji zależności zostanie utworzony podkatalog o nazwie node_modules

2.3. Udostępnianie plików HTML, CSS i kodu JavaScript działającego po stronie klienta Jak wcześniej wspomniano, budowana tutaj aplikacja czatu powinna oferować wymienione poniżej możliwości: Udostępnianie plików statycznych przeglądarce internetowej użytkownika. Obsługę przez serwer wiadomości związanych z czatem. Obsługę wiadomości czatu w przeglądarce internetowej użytkownika. Logika aplikacji będzie obsługiwana przez wiele plików, część wykorzystywanych po stronie serwera, inne po stronie klienta, jak pokazano na rysunku 2.8. Pliki kodu JavaScript działającego po stronie klienta muszą być udostępniane w postaci zasobów statycznych, a nie przetwarzane przez Node.

W tym podrozdziale zajmiemy się spełnieniem pierwszego wymagania stawianego aplikacji: zdefiniujemy logikę niezbędną do udostępniania plików statycznych. Następnie dodamy pliki statyczne z kodem HTML i CSS.

Rysunek 2.8. W budowanej aplikacji czatu po stronie zarówno serwera, jak i klienta istnieje logika JavaScript

2.3.1. Tworzenie podstawowego serwera plików statycznych Aby utworzyć serwer plików statycznych, konieczne jest wykorzystanie pewnych wbudowanych funkcji Node, a także opracowanego przez firmę trzecią modułu mime w celu ustalenia typu MIME danego pliku. Rozpoczynamy od głównego pliku aplikacji. W katalogu głównym projektu utwórz plik o nazwie server.js i umieść w nim deklaracje zmiennych

przedstawione w listingu 2.2. Wspomniane deklaracje pozwalają na uzyskanie dostępu do funkcji HTTP w Node, a także na pracę z systemem plików, użycie funkcji związanych ze ścieżkami dostępu do plików oraz możliwość ustalenia typu MIME danego pliku. Zmienną cache wykorzystamy do buforowania danych pliku. Listing 2.2. Deklaracje zmiennych var http = require('http'); Wbudowany moduł http dostarcza funkcje dotyczące serwera i klienta. var fs = require('fs'); Wbudowany moduł fs dostarcza funkcje przeznaczone do pracy z systemem plików. var path = require('path'); Wbudowany moduł path dostarcza funkcje przeznaczone do pracy ze ścieżkami dostępu systemu plików. var mime = require('mime'); Dodatkowy moduł mime zapewnia możliwość ustalenia typu MIME na podstawie rozszerzenia pliku. var cache = {}; Obiekt cache służy do przechowywania buforowanych plików

Wysyłanie danych pliku i odpowiedzi w postaci błędów Kolejnym krokiem jest utworzenie trzech funkcji pomocniczych używanych do udostępniania statycznych plików HTTP. Pierwsza powoduje wygenerowanie błędu o kodzie 404, jeśli żądany plik nie istnieje. W pliku server.js umieść więc poniższy kod funkcji pomocniczej: function send404(response) { response.writeHead(404, {'Content-Type': 'text/plain'}); response.write('Błąd 404: plik nie został znaleziony.'); response.end(); }

Druga funkcja pomocnicza dostarcza dane pliku. Najpierw przygotowuje odpowiednie nagłówki HTTP, a następnie wysyła zawartość pliku. W pliku server.js umieść więc poniższy kod: function sendFile(response, filePath, fileContents) { response.writeHead( 200, {"content-type": mime.lookup(path.basename(filePath))} ); response.end(fileContents); }

Uzyskanie dostępu do pamięci operacyjnej (RAM) jest szybsze niż do systemu plików. Dlatego też aplikacje Node buforują w pamięci często używane dane. Budowana tutaj aplikacja czatu będzie buforowała w pamięci pliki statyczne i odczyta je z dysku tylko podczas pierwszego ich żądania. Trzecia funkcja

pomocnicza sprawdza więc, czy plik jest buforowany, a następnie go udostępnia. Jeżeli plik nie jest jeszcze buforowany, wtedy zostanie odczytany z dysku i udostępniony. Jeżeli plik nie istnieje, odpowiedzią będzie kod błędu HTTP 404. W pliku server.js umieść więc kod funkcji pomocniczej przedstawionej w listingu 2.3. Listing 2.3. Funkcja pomocnicza udostępniająca pliki statyczne function serveStatic(response, cache, absPath) { Sprawdzenie, czy plik jest buforowany w pamięci. if (cache[absPath]) { sendFile(response, absPath, cache[absPath]); Udostępnienie pliku z pamięci. } else { fs.exists(absPath, function(exists) { Sprawdzenie, czy plik istnieje. if (exists) { fs.readFile(absPath, function(err, data) { Odczyt pliku z dysku. if (err) { send404(response); } else { cache[absPath] = data; sendFile(response, absPath, data); Udostępnienie pliku odczytanego z dysku. } }); } else { send404(response); Wysłanie odpowiedzi HTTP 404. } }); } }

Tworzenie serwera HTTP W przypadku serwera HTTP funkcja anonimowa dostarczana jako argument funkcji createServer() działa w charakterze wywołania definiującego sposób obsługi poszczególnych żądań HTTP. Funkcja wywołania zwrotnego akceptuje dwa argumenty: request i response. Podczas jej wywołania serwer HTTP wypełni wspomniane argumenty obiektami, które pozwolą na odpowiednio odczyt szczegółów żądania oraz przygotowanie odpowiedzi. Więcej informacji o module http Node znajdziesz w rozdziale 4. W pliku server.js umieść kod przedstawiony w listingu 2.4, odpowiedzialny za utworzenie serwera HTTP.

Listing 2.4. Logika tworząca serwer HTTP var server = http.createServer(function(request, response) { Utworzenie serwera HTTP za pomocą funkcji anonimowej definiującej zachowanie w poszczególnych żądaniach. var filePath = false; if (request.url == '/') { Wskazanie pliku HTML, który ma być domyślnie udostępniany. filePath = 'public/index.html'; } else { filePath = 'public' + request.url; Zamiana adresu URL na względną ścieżkę dostępu do pliku. } var absPath = './' + filePath; serveStatic(response, cache, absPath); Udostępnienie pliku statycznego. });

Uruchomienie serwera HTTP W kodzie utworzyliśmy serwer HTTP, ale nie dodaliśmy jeszcze logiki niezbędnej do jego uruchomienia. Poniższy fragment kodu powoduje uruchomienie serwera i nasłuchiwanie TCP/IP na porcie 3000. Port 3000 został wybrany dowolnie, można skorzystać z każdego nieużywanego portu o numerze większym niż 1024 (port 1024 również może działać, jeśli korzystasz z systemu Windows, natomiast w systemach Linux i OS X konieczne jest wówczas uruchomienie aplikacji przez użytkownika uprzywilejowanego, na przykład root). server.listen(3000, function() { console.log("Serwer nasłuchuje na porcie 3000."); });

Jeżeli chcesz się przekonać, jak aplikacja działa na tym etapie, to możesz uruchomić serwer przez wydanie poniższego polecenia w wierszu poleceń: node server.js

Po uruchomieniu serwera przejście pod adres http://127.0.0.1:3000 w przeglądarce internetowej spowoduje wywołanie funkcji pomocniczej generującej kod błędu 404, a więc wyświetlenie komunikatu Błąd 404: plik nie został znaleziony. Wprawdzie aplikacja zawiera logikę odpowiedzialną za obsługę plików statycznych, ale jeszcze nie dodaliśmy żadnego tego rodzaju pliku. Warto w tym miejscu wspomnieć, że działanie serwera można zatrzymać przez naciśnięcie klawiszy Ctrl+C w powłoce. Przechodzimy teraz do dodania plików statycznych zapewniających nieco większą funkcjonalność aplikacji czatu.

2.3.2. Dodanie plików HTML i CSS

Pierwszy dodawany plik statyczny zawiera kod HTML. W katalogu public utwórz plik o nazwie index.html, a następnie umieść w nim kod HTML przedstawiony w listingu 2.5. Wspomniany kod HTML powoduje dołączenie pliku arkusza stylów (CSS), zdefiniowanie pewnych elementów HTML przeznaczonych do wyświetlania treści aplikacji, a także do wczytania kilku plików zawierających kod JavaScript działający po stronie klienta. Pliki JavaScript zapewniają dostęp do funkcji biblioteki Socket.IO, biblioteki jQuery (w celu łatwiejszej pracy z modelem DOM), a także oferują przygotowane specjalnie dla budowanej aplikacji funkcje obsługi czatu. Listing 2.5. Kod HTML aplikacji czatu

Czat

Element , w którym będzie wyświetlona nazwa aktualnego pokoju czatu. Element , w którym będzie wyświetlona lista dostępnych pokoi czatu.

Element , w którym będą wyświetlone wiadomości czatu.

Element formularza, w którym użytkownik będzie wydawał polecenia i wpisywał wiadomości.

Polecenia czatu:

  • Zmiana nazwy użytkownika: /nick [nazwa użytkownika]
  • Przejście do lub utworzenie pokoju: /join [nazwa pokoju]








Kolejny plik, który trzeba dodać, zawiera style CSS używane w aplikacji. W katalogu public/stylesheets utwórz plik o nazwie style.css, a następnie umieść w nim kod CSS przedstawiony w listingu 2.6. Listing 2.6. Kod CSS używany przez aplikację body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } a { color: #00B7FF; } #content { Aplikacja będzie miała szerokość 800 pikseli i zostanie wyśrodkowana poziomo. width: 800px; margin-left: auto; margin-right: auto; } #room { Reguła CSS dla elementu, w którym wyświetlana jest nazwa aktualnego pokoju czatu. background-color: #ddd; margin-bottom: 1em; } #messages { Element wiadomości ma szerokość 690 pikseli i wysokość 300 pikseli. width: 690px; height: 300px; overflow: auto; Element wyświetlający wiadomości czatu będzie mógł być przewijany, gdy wiadomości całkowicie go wypełnią. background-color: #eee; margin-bottom: 1em; margin-right: 10px; }

Po dodaniu plików HTML i CSS możesz uruchomić aplikację w przeglądarce internetowej. Na obecnym etapie prac powinna wyglądać jak na rysunku 2.9. Aplikacja oczywiście nie oferuje jeszcze pełnej funkcjonalności, ale pliki

statyczne są udostępniane, a podstawowy układ graficzny prawidłowo generowany. Przechodzimy więc teraz do przygotowania kodu działającego po stronie serwera i odpowiedzialnego za obsługę wiadomości.

2.4. Obsługa wiadomości czatu za pomocą biblioteki Socket.IO Z trzech wymagań stawianych budowanym aplikacjom omówiliśmy dotąd pierwsze, czyli udostępnianie plików statycznych. Przechodzimy teraz do drugiego — obsługi komunikacji między przeglądarką internetową i serwerem. Nowoczesne przeglądarki internetowe mają możliwość użycia technologii WebSocket do obsługi komunikacji między przeglądarką i serwerem. (Dokładne informacje dotyczące obsługi WebSocket w przeglądarkach internetowych znajdziesz na stronie http://socket.io/#browser-support).

Rysunek 2.9. Aplikacja czatu na obecnym etapie prac

WebSocket zapewnia warstwę abstrakcji dla siebie oraz dla innych mechanizmów transportu, zarówno dla Node, jak i kodu JavaScript działającego po stronie klienta. Biblioteka Socket.IO automatycznie zapewnia rozwiązania awaryjne, jeśli obsługa WebSocket nie została zaimplementowana w

przeglądarce internetowej. We wszystkich przypadkach używane jest to samo API. W tym podrozdziale: pokrótce poznasz bibliotekę Socket.IO oraz zdefiniujesz funkcje Socket.IO niezbędne po stronie serwera; dodasz kod odpowiedzialny za konfigurację serwera Socket.IO; dodasz kod odpowiedzialny za obsługę różnych zdarzeń czatu. Biblioteka Socket.IO standardowo oferuje wirtualne kanały, więc zamiast rozgłaszać każdą wiadomość do wszystkich połączonych użytkowników, można ją przekazać jedynie do tych, którzy są subskrybentami danego kanału. Dzięki tej funkcji implementacja pokoi czatu w budowanej tutaj aplikacji staje się naprawdę łatwym zadaniem, o czym się wkrótce przekonasz. Biblioteka Socket.IO to również doskonały przykład użyteczności emiterów zdarzeń. Wspomniany emiter zdarzeń to w zasadzie użyteczny wzorzec organizacji logiki asynchronicznej. W tym rozdziale poznasz kod pewnych emiterów zdarzeń, ale tym tematem dokładnie zajmiemy się w następnym rozdziale. Emiter zdarzeń Pod względem koncepcji emiter zdarzeń jest powiązany z pewnego rodzaju zasobem, może wysyłać i otrzymywać wiadomości do oraz z zasobu. Wspomnianym zasobem może być połączenie ze zdalnym serwerem lub coś znacznie bardziej abstrakcyjnego, na przykład postać w grze. Projekt Johnny-Five (https://github.com/rwaldron/johnny-five) wykorzystuje Node w aplikacjach robotów i używa emiterów zdarzeń do kontrolowania mikrokontrolerów Arduino.

W pierwszej kolejności trzeba uruchomić serwer i przygotować logikę odpowiedzialną za nawiązywanie połączenia. Następnie przystąpimy do zdefiniowania funkcji wymaganych po stronie serwera.

2.4.1. Konfiguracja serwera Socket.IO Na początku w pliku server.js należy umieścić dwa podane poniżej wiersze kodu. Pierwszy powoduje wczytanie funkcji ze wskazanego modułu Node dostarczającego logikę potrzebną do obsługi po stronie serwera funkcji czatu związanych z biblioteką Socket.IO. Wskazany moduł zdefiniujemy za chwilę. Natomiast drugi wiersz uruchamia serwer i dostarcza funkcje Socket.IO przy założeniu, że mamy już zdefiniowany serwer HTTP, co pozwala na współdzielenie tego samego portu TCP/IP: var chatServer = require('./lib/chat_server'); chatServer.listen(server);

Teraz trzeba utworzyć plik chat_server.js w podkatalogu lib. Na początku w

wymienionym pliku umieść poniższe deklaracje zmiennych. Te deklaracje pozwolą na użycie biblioteki Socket.IO, a także inicjalizują kilka zmiennych przeznaczonych do definiowania stanu czatu: var socketio = require('socket.io'); var io; var guestNumber = 1; var nickNames = {}; var namesUsed = []; var currentRoom = {};

Utworzenie logiki połączenia Kolejnym krokiem jest dodanie przedstawionej w listingu 2.7 logiki odpowiedzialnej za zdefiniowanie funkcji listen serwera czatu. Wymieniona funkcja jest wywoływana w pliku server.js. Powoduje uruchomienie serwera Socket.IO, ogranicza ilość danych wyświetlanych w konsoli przez bibliotekę Socket.IO, a także definiuje sposób obsługi wszystkich połączeń przychodzących. Listing 2.7. Logika odpowiedzialna za uruchomienie serwera Socket.IO exports.listen = function(server) { io = socketio.listen(server); Uruchomienie serwera Socket.IO i umożliwienie mu współpracy z istniejącym serwerem HTTP. io.set('log level', 1); io.sockets.on('connection', function (socket) { Zdefiniowanie sposobu obsługi połączenia użytkownika. guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed); Przypisanie użytkownikowi nazwy gościa podczas nawiązywania połączenia. joinRoom(socket, 'Lobby'); Umieszczenie użytkownika w pokoju Lobby, gdy próbuje on nawiązać połączenie. handleMessageBroadcasting(socket, nickNames); Obsługa wiadomości użytkownika, prób zmiany nazwy użytkownika, a także tworzenia lub zmiany pokoju czatu. handleNameChangeAttempts(socket, nickNames, namesUsed); handleRoomJoining(socket); socket.on('rooms', function() { Wyświetlenie użytkownika wraz z listą pokoi, w których prowadzi czat. socket.emit('rooms', io.sockets.manager.rooms); }); handleClientDisconnection(socket, nickNames, namesUsed); Zdefiniowanie logiki wykonywanej podczas rozłączania użytkownika. }); };

Jak możesz zauważyć, logika obsługi połączenia wywołuje wiele funkcji

pomocniczych, które teraz trzeba będzie zdefiniować w pliku chat_server.js. Po przygotowaniu logiki odpowiedzialnej za nawiązywanie połączenia trzeba zdefiniować kilka funkcji pomocniczych, które obsługują inne funkcjonalności oferowane przez aplikację.

2.4.2. Obsługa zdarzeń oraz scenariuszy w aplikacji Aplikacja czatu musi zapewnić obsługę wymienionych poniżej zdarzeń i rodzajów scenariuszy: przypisanie nazwy gościa, żądanie zmiany pokoju, żądanie zmiany nazwy użytkownika, wysyłanie wiadomości czatu, tworzenie pokoju, rozłączanie użytkownika. Do obsługi wymienionych funkcji dodatkowych funkcji pomocniczych.

konieczne

jest

przygotowanie

kilku

Przypisanie nazwy gościa Pierwsza funkcja pomocnicza, którą trzeba dodać, nosi nazwę assignGuestName() i jest odpowiedzialna za obsługę nadawania nazwy nowemu użytkownikowi. Kiedy użytkownik po raz pierwszy nawiązuje połączenie z serwerem czatu, zostaje umieszczony w pokoju Lobby. Jednocześnie następuje wywołanie funkcji assignGuestName() i przypisanie mu nazwy odróżniającej nowego użytkownika od pozostałych. Nazwa każdego gościa to w zasadzie słowo Gość, po którym znajduje się liczba o wartości zwiększanej po nawiązaniu połączenia przez każdego kolejnego użytkownika. Nazwa gościa jest przechowywana w zmiennej nickNames powiązanej z wewnętrznym identyfikatorem gniazda. Ponadto nazwa zostaje dodana do namesUsed, czyli zmiennej zawierającej użyte dotąd nazwy użytkowników. Implementacja funkcji assignGuestName() została przedstawiona w listingu 2.8, dodaj ją do pliku lib/chat_server.js. Listing 2.8. Przypisanie nazwy gościa function assignGuestName(socket, guestNumber, nickNames, namesUsed) { var name = 'Gość' + guestNumber; Wygenerowanie nowej nazwy gościa .

nickNames[socket.id] = name; Powiązanie nazwy gościa z identyfikatorem połączenia klienta. socket.emit('nameResult', { Podanie użytkownikowi wygenerowanej dla niego nazwy. success: true, name: name }); namesUsed.push(name); Zwróć uwagę na użycie nazwy gościa. return guestNumber + 1; Inkrementacja licznika używanego podczas generowania nazw gości. }

Dołączanie do pokoju Druga funkcja pomocnicza, którą trzeba dodać do pliku chat_server.js, nosi nazwę joinRoom(). Kod wymienionej funkcji przedstawiono w listingu 2.9. Jest ona odpowiedzialna za obsługę logiki związanej z dołączaniem użytkownika do pokoju czatu. Listing 2.9. Logika obsługująca dołączanie do pokoju function joinRoom(socket, room) { Dołączenie uczestnika do pokoju. socket.join(room); currentRoom[socket.id] = room; Zauważ, że użytkownik znajduje się w pokoju. socket.emit('joinResult', {room: room}); Poinformowanie uczestnika, że znajduje się we wskazanym pokoju. socket.broadcast.to(room).emit('message', { Poinformowanie pozostałych uczestników w pokoju o dołączeniu nowego. text: nickNames[socket.id] + ' dołączył do pokoju ' + room + '.' }); var usersInRoom = io.sockets.clients( room); uczestnicy znajdują się w danym pokoju.

Ustalenie, czy jeszcze inni

if (usersInRoom.length > 1) { Jeżeli w pokoju są inni uczestnicy, aplikacja wyświetla ich liczbę. var usersInRoomSummary = 'Lista użytkowników w pokoju ' + room + ': '; for (var index in usersInRoom) { var userSocketId = usersInRoom[index].id; if (userSocketId != socket.id) { if (index > 0) { usersInRoomSummary += ', '; } usersInRoomSummary += nickNames[userSocketId]; } }

usersInRoomSummary += '.'; socket.emit('message', {text: usersInRoomSummary}); Przekazanie nowemu uczestnikowi podsumowania o innych uczestnikach znajdujących się w pokoju. } }

Dzięki bibliotece Socket.IO dołączenie uczestnika do pokoju czatu jest prostą operacją i wymaga jedynie wywołania metody join obiektu socket. Następnie aplikacja podaje informacje szczegółowe uczestnikowi oraz pozostałym uczestnikom znajdującym się w tym samym pokoju. Aplikacja podaje liczbę osób znajdujących się w pokoju czatu, a także informuje pozostałych uczestników w pokoju o dołączeniu nowego.

Obsługa żądań zmiany nazwy użytkownika Jeżeli każdy uczestnik zachowa przydzieloną mu nazwę gościa, wtedy trudno będzie połapać się, kto jest kim. Dlatego też aplikacja czatu pozwala użytkownikowi na zmianę jego nazwy. Jak pokazano na rysunku 2.10, zmiana nazwy powoduje wykonanie przez przeglądarkę internetową użytkownika żądania za pomocą Socket.IO, a następnie otrzymanie odpowiedzi wskazującej na sukces lub niepowodzenie operacji.

Rysunek 2.10. Żądanie zmiany nazwy użytkownika i odpowiedź negatywna

W pliku lib/chat_server.js umieść kod przedstawiony w listingu 2.10 zawierający definicję funkcji odpowiedzialnej za obsługę żądań zmiany nazwy użytkownika. Z perspektywy aplikacji użytkownik nie może zmienić nazwy na rozpoczynającą się od słowa Gość lub będącej już w użyciu. Listing 2.10. Logika odpowiedzialna za obsługę zmiany nazwy użytkownika function handleNameChangeAttempts(socket, nickNames, namesUsed) { socket.on('nameAttempt', function(name) { Dodanie funkcji nasłuchującej zdarzeń

nameAttempt. if (name.indexOf('Gość') == 0) { Niedozwolone jest użycie nazwy rozpoczynającej się od słowa Gość. socket.emit('nameResult', { success: false, message: 'Nazwa użytkownika nie może rozpoczynać się od słowa "Gość".' }); } else { if (namesUsed.indexOf(name) == -1) { Jeżeli nazwa nie jest jeszcze zarejestrowana, wtedy należy ją zarejestrować. var previousName = nickNames[socket.id]; var previousNameIndex = namesUsed.indexOf(previousName); namesUsed.push(name); Usunięcie poprzedniej nazwy użytkownika i tym samym udostępnienie jej innym klientom. nickNames[socket.id] = name; delete namesUsed[previousNameIndex]; socket.emit('nameResult', { success: true, name: name }); socket.broadcast.to(currentRoom[socket.id]).emit('message', { text: previousName + ' zmienił nazwę na ' + name + '.' }); } else { socket.emit('nameResult', { Wygenerowanie błędu, jeśli wybrana nazwa jest już używana przez innego użytkownika. success: false, message: 'Ta nazwa jest używana przez innego użytkownika.' }); } } }); }

Wysyłanie wiadomości czatu Kiedy zadbaliśmy już o nazwy użytkowników, przechodzimy do dodania kolejnej funkcji. Odpowiada ona za obsługę sposobu wysyłania wiadomości czatu. Na rysunku 2.11 pokazano podstawę działania tego procesu: użytkownik emituje zdarzenie wskazujące pokój, do którego ma zostać wysłana wiadomość, oraz jej tekst. Następnie serwer przekazuje wiadomość do wszystkich uczestników

czatu znajdujących się w danym pokoju.

Rysunek 2.11. Wysyłanie wiadomości czatu

Poniższy kod umieść w pliku lib/chat_server.js. Do przekazywania wiadomości jest używana funkcja broadcast() biblioteki Socket.IO: function handleMessageBroadcasting(socket) { socket.on('message', function (message) { socket.broadcast.to(message.room).emit('message', { text: nickNames[socket.id] + ': ' + message.text }); }); }

Tworzenie pokoju Kolejnym krokiem jest dodanie funkcji pozwalającej użytkownikowi na dołączenie do istniejącego pokoju czatu lub utworzenie nowego. Na rysunku 2.12 pokazano interakcje zachodzące wówczas między użytkownikiem i serwerem.

Rysunek 2.12. Przejście do innego pokoju czatu

Poniższy kod umieść w pliku lib/chat_server.js, umożliwiając tym samym zmianę pokoju czatu. Zwróć uwagę na użycie metody leave() biblioteki Socket.IO: function handleRoomJoining(socket) { socket.on('join', function(room) { socket.leave(currentRoom[socket.id]); joinRoom(socket, room.newRoom); }); }

Obsługa rozłączenia użytkownika Do pliku lib/chat_server.js konieczne jest dodanie poniższego fragmentu kodu odpowiedzialnego za usunięcie nazwy użytkownika ze zmiennych nickNames i namesUsed, gdy użytkownik kończy pracę z aplikacją czatu: function handleClientDisconnection(socket) { socket.on('disconnect', function() { var nameIndex = namesUsed.indexOf(nickNames[socket.id]); delete namesUsed[nameIndex]; delete nickNames[socket.id]; }); }

W ten sposób zakończyliśmy tworzenie komponentów działających po stronie serwera. Teraz możemy powrócić do kontynuowania prac nad logiką działającą po stronie klienta.

2.5. Użycie kodu JavaScript działającego po

stronie klienta do utworzenia interfejsu użytkownika aplikacji Po dodaniu działającej po stronie serwera logiki Socket.IO do obsługi wiadomości czatu pora dodać kod JavaScript działający po stronie klienta i potrzebny do prowadzenia komunikacji z serwerem. Wspomniany kod musi zapewnić następujące możliwości: wysyłanie do serwera wiadomości oraz żądań zmiany pokoju lub nazwy użytkownika; wyświetlanie wiadomości pochodzących od innych uczestników czatu oraz listy dostępnych pokoi. Rozpoczniemy do implementacji pierwszej z wymienionych powyżej funkcji.

2.5.1. Przekazywanie do serwera wiadomości oraz żądań zmiany pokoju lub nazwy użytkownika Pierwszym fragmentem kodu JavaScript działającego po stronie klienta jest prototyp obiektu JavaScript, który będzie przetwarzał polecenia czatu, wysyłał wiadomości oraz żądania zmiany pokoju lub nazwy użytkownika. W katalogu public/javascripts utwórz plik o nazwie chat.js i umieść w nim poniższy fragment kodu. W języku JavaScript służy on do zdefiniowania „klasy” pobierającej podczas tworzenia pojedynczy argument w postaci gniazda Socket.IO: var Chat = function(socket) { this.socket = socket; };

Następnie dodaj poniższą funkcję odpowiedzialną za wysyłanie wiadomości: Chat.prototype.sendMessage = function(room, text) { var message = { room: room, text: text }; this.socket.emit('message', message); };

A teraz dodaj funkcję przeznaczoną do obsługi zmiany pokoju: Chat.prototype.changeRoom = function(room) {

this.socket.emit('join', { newRoom: room }); };

Na końcu dodaj funkcję przedstawioną w listingu 2.11 i przeznaczoną do przetwarzania poleceń czatu. Rozpoznawane są dwa polecenia: join pozwalające na dołączenie się do pokoju lub utworzenie nowego oraz nick pozwalające na zmianę nazwy użytkownika. Listing 2.11. Funkcja przetwarzająca polecenia czatu Chat.prototype.processCommand = function(command) { var words = command.split(' '); var command = words[0] .substring(1, words[0].length) .toLowerCase(); Przetworzenie polecenia z listy słów. var message = false; switch(command) { case 'join': words.shift(); var room = words.join(' '); this.changeRoom(room); Obsługa operacji zmiany pokoju lub utworzenia nowego. break; case 'nick': words.shift(); var name = words.join(' '); this.socket.emit('nameAttempt', name); Obsługa operacji zmiany nazwy użytkownika. break; default: message = 'Nieznane polecenie.'; Jeżeli polecenie nie zostanie rozpoznane, wtedy nastąpi wygenerowanie błędu. break; }; return message; };

2.5.2. Wyświetlenie w interfejsie użytkownika wiadomości i listy dostępnych pokoi

W tym punkcie zajmiemy się dodaniem logiki odpowiedzialnej za bezpośrednią współpracę z opartym na przeglądarce interfejsem użytkownika za pomocą biblioteki jQuery. Pierwsza funkcja, nad którą będziemy pracować, służy do wyświetlania danych tekstowych. Z perspektywy zapewnienia bezpieczeństwa w aplikacji sieciowej rozróżniamy dwa rodzaje danych tekstowych. Pierwszy to zaufane dane tekstowe, na które składają się dane pochodzące z aplikacji. Drugi to niezaufane dane tekstowe, które pochodzą od użytkownika lub powstały na podstawie danych podanych przez użytkownika. Dane tekstowe pochodzące od użytkownika są uznawane za niezaufane, ponieważ złośliwy użytkownik może celowo umieścić w nich z na cz niki "}; console.log(ejs.render(template, context));

Powyższy fragment kodu powoduje wygenerowanie następujących danych wyjściowych:

Jeżeli ufasz danym używanym w szablonie i nie chcesz zmieniać znaczenia znaków w wartościach kontekstu w szablonie EJS, wtedy w znaczniku szablonu można użyć — zamiast nich można zastosować wcięcia oznaczające zagnieżdżanie znaczników. Znacznik może mieć przypisaną jedną lub więcej klas CSS przez dodanie . . Element wraz z przypisanymi klasami content i sidebar można przedstawić w następujący sposób: div.content.sidebar

Identyfikatory CSS znaczników są przypisywane przez dodanie #. Na przykład dodanie identyfikatora CSS o nazwie featured_content do poprzedniego przykładu odbywa się następująco: div.content.sidebar#featured_content Skrót dla znacznika Ponieważ znacznik jest powszechnie używany w kodzie HTML, Jade oferuje możliwość wskazania go za pomocą skrótu. Przedstawione poniżej wywołanie powoduje wygenerowanie takiego samego kodu HTML jak w poprzednim przykładzie: .content.sidebar#featured_content

Skoro już wiesz, jak wskazywać znaczniki HTML oraz przypisywać im identyfikatory i klasy CSS, zobacz, jak wskazać atrybuty HTML.

Podawanie atrybutów znacznika Atrybut znacznika można podać przez jego umieszczenie w nawiasie okrągłym,

a specyfikacje poszczególnych atrybutów należy rozdzielić przecinkami. Dlatego też że pomocą poniższego kodu Jade możesz wskazać łącze, które będzie otwierane w nowej karcie: a(href='http://nodejs.org', target='_blank')

Ponieważ specyfikacja atrybutów znaczników może oznaczać wiele wierszy kodu Jade, silnik szablonów oferuje na tym polu pewną elastyczność. Przedstawiony poniżej przykład to prawidłowy kod Jade będący odpowiednikiem poprzedniego przykładu: a(href='http://nodejs.org', target='_blank')

Istnieje również możliwość podania atrybutów niewymagających wartości. W kolejnym przykładzie Jade pokazano specyfikację formularza HTML zawierającego element select wraz z opcją wskazującą na wybrany element: strong Wybierz ulubioną potrawę: form select option(value='Ser') Ser option(value='Tofu', selected) Tofu

Podanie treści znacznika W poprzednim fragmencie kodu pokazano przykład treści znacznika (Wybierz ulubioną potrawę) po znaczniku strong: Ser po pierwszym znaczniku option i Tofu po drugim. To standardowy sposób podawania treści znacznika w Jade, ale nie jedyny. Wprawdzie tego rodzaju styl doskonale sprawdza się w przypadku niewielkiej treści, ale może spowodować, że wiersze szablonu Jade staną się bardzo długie, jeśli treść znacznika będzie obszerna. Na szczęście, jak pokazano w poniższym przykładzie, treść znacznika można w Jade podawać także za pomocą znaku |: textarea | To jest pewien tekst domyślny, | który powinien zostać | wyświetlony użytkownikowi.

Jeżeli znacznik HTML, na przykład style lub script, akceptuje jedynie tekst (to znaczny nie zezwala na zagnieżdżanie elementów HTML), wówczas znaki | można zupełnie pominąć, jak przedstawiono w poniższym przykładzie: style h1 { font-size: 6em; color: #9DFF0C;

}

Istnienie dwóch oddzielnych sposobów podawania dłuższej i krótszej treści znaczników pomaga w zachowaniu eleganckiego wyglądu znaczników Jade. Ponadto Jade obsługuje alternatywny sposób wyrażania zagnieżdżeń, nazywany rozwinięciem bloku.

Zachowanie organizacji dzięki rozwinięciu bloku Standardowo zagnieżdżenia są wyrażane w Jade przez zastosowanie wcięć, ale czasami wcięcia mogą doprowadzić do powstawania dużej ilości pustego miejsca. Jako przykład poniżej przedstawiono szablon Jade używający wcięć do zdefiniowania prostej listy łączy: ul li a(href='http://nodejs.org/') Strona główna Node.js li a(href='http://npmjs.org/') Strona główna NPM li a(href='http://nodebits.org/') Blog Nodebits

Znacznie bardziej zwięzłym sposobem wyrażenia poprzedniego przykładu jest użycie oferowanego przez Jade mechanizmu rozwinięcia bloku. W takim przypadku po znaczniku umieszcza się dwukropek oznaczający zagnieżdżenie. Przedstawiony poniżej kod Jade generuje takie same dane wyjściowe jak poprzedni listing, ale składa się z czterech wierszy zamiast z siedmiu: ul li: a(href='http://nodejs.org/') Strona główna Node.js li: a(href='http://npmjs.org/') Strona główna NPM li: a(href='http://nodebits.org/') Blog Nodebits

Teraz już wiersz, jak przedstawić znaczniki za pomocą Jade. Przechodzimy więc do zagadnienia integracji Jade z aplikacją sieciową.

Umieszczanie danych w szablonach Jade Do silnika Jade dane są przekazywane w taki sam podstawowy sposób jak do EJS. Szablon zostaje w pierwszej kolejności skompilowany na postać funkcji, która następnie będzie wywoływana dla kontekstu w celu wygenerowania danych wyjściowych HTML. Przykład przedstawiono poniżej: var jade = require('jade'); var template = 'strong #{message}'; var context = {message: 'Witaj, szablonie!'}; var fn = jade.compile(template);

console.log(fn(context));

W poprzednim przykładzie kod #{message} w szablonie wskazywał miejsce zarezerwowane, które zostanie zastąpione przez wartość pochodzącą z kontekstu. Wartości kontekstu można również używać w celu dostarczania wartości atrybutów. Przedstawiony poniżej kod spowoduje wygenerowanie znacznika : var jade = require('jade'); var template = 'a(href = url)'; var context = {url: 'http://google.pl'}; var fn = jade.compile(template); console.log(fn(context));

W ten sposób dowiedziałeś się, jak kod HTML można podać za pomocą Jade, a także jak dostarczać szablonom Jade danych aplikacji. Przechodzimy więc do wykorzystania logiki w Jade.

11.4.2. Logika w szablonach Jade Po dostarczeniu szablonowi Jade danych aplikacji potrzebna jest logika, która będzie mogła przetworzyć wspomniane dane. Jade pozwala na bezpośrednie osadzanie wierszy kodu JavaScript w szablonach, co stanowi doskonały sposób na zdefiniowanie logiki. Powszechnie stosuje się konstrukcje takie jak instrukcje if, pętle for i deklaracje var. Zanim przejdziemy do szczegółów, spójrz na przykład szablonu Jade generującego listę kontaktową. W ten sposób możesz się przekonać, jak używać logiki Jade w aplikacji: h3.contacts-header Moja lista kontaktów if contacts.length each contact in contacts - var fullName = contact.firstName + ' ' + contact.lastName .contact-box p fullName if contact.isEditable p: a(href='/edit/+contact.id) Edycja rekordu p case contact.status when 'Aktywny' strong Użytkownik jest aktywny w systemie when 'Nieaktywny'

em Użytkownik jest nieaktywny w systemie when 'Oczekujący' | Oczekiwanie na akceptację zaproszenia else p Twoja lista kontaktów jest obecnie pusta

Najpierw opiszemy różne sposoby, na jakie Jade obsługuje dane wyjściowe podczas przetwarzania osadzonego kodu JavaScript.

Użycie JavaScript w szablonach Jade Poprzedzenie wiersza kodu JavaScript prefiksem - powoduje wykonanie go bez umieszczenia jakiejkolwiek wartości zwrotnej tego kodu w danych wyjściowych szablonu. Z kolei poprzedzenie logiki JavaScript znakiem = spowoduje dołączenie wartości zwrotnej kodu, przy czym znaki specjalne zostaną zneutralizowane w celu ochrony przed atakami typu XSS. Jeżeli kod JavaScript generuje dane wyjściowe, które nie powinny być modyfikowane, należy zastosować prefiks !=. Podsumowanie prefiksów przedstawiono w tabeli 11.1. Tabela 11.1. Prefiksy stosowane wraz z kodem JavaScript osadzonym w szablonie Jade Prefiks Opis danych wyjściowych

=

Dane wyjściowe zostaną zneutralizowane (w przypadku niezaufanych lub nieprzewidywalnych wartości to rodzaj zabezpieczenia przed atakami XSS).

!=

Dane wyjściowe pozostają niezmodyfikowane (w przypadku zaufanych lub przewidywalnych wartości).

-

Brak danych wyjściowych.

Jade zawiera wiele najczęściej używanych poleceń warunkowych i iteracji, które mogą być zapisywane bez prefiksów: if, else if, else, case, when, default, until, while, each i unless. Istnieje również możliwość definiowania zmiennych w Jade. Poniższe dwa odpowiadające sobie polecenia pokazują, jak można przypisać wartość w Jade: - var count = 0 count = 0

Pozbawione prefiksu polecenie nie generuje danych wyjściowych, podobnie jak w przypadku użycia prefiksu -, co wcześniej omówiono.

Iteracja przez obiekty i tablice Wartości przekazywane w kontekście są dostępne dla kodu JavaScript w szablonie Jade. W kolejnym przykładzie odczytujemy szablon Jade z pliku, a następnie przekazujemy szablonowi kontekst zawierający tablicę z kilkoma komunikatami, które mają być wyświetlone: var jade = require('jade');

var fs = require('fs'); var template = fs.readFileSync('./template.jade'); var context = { messages: [ 'Logowanie zakończone powodzeniem.', 'Witamy ponownie!' ]}; var fn = jade.compile(template); console.log(fn(context));

Szablon Jade przedstawia się następująco: - messages.forEach(function(message) { p= message - })

Ostateczne dane wyjściowe HTML mają postać przedstawioną poniżej:

Logowanie zakończone powodzeniem.

Witamy ponownie!



Jade obsługuje iterację w formie innej niż stosowana w JavaScript: za pomocą polecenia each. Wspomniane polecenie each pozwala na łatwą iterację przez tablicę i właściwości obiektu. Przedstawiony poniżej kod jest odpowiednikiem poprzedniego przykładu, ale używa polecenia each: each message in messages p= message

Iterację przez właściwości obiektu można przeprowadzić, stosując niewielką zmianę polecenia, na przykład: each value, key in post div strong #{key} p value

Warunkowe wygenerowanie kodu szablonu Czasami w zależności od wartości danych szablon musi „podejmować decyzje” co do sposobu ich wyświetlania. Kolejny przykład ilustruje tego rodzaju sytuację, w której mniej więcej co drugi raz skrypt generuje dane w postaci kodu HTML: - var n = Math.round(Math.random() * 1) + 1 - if (n == 1) { script alert('Wygrałeś!'); -}

Polecenia warunkowe mogą być w Jade zapisane także w alternatywnej, nieco bardziej przejrzystej formie: - var n = Math.round(Math.random() * 1) + 1 if n == 1 script alert('Wygrałeś!');

Jeżeli tworzysz negację warunku, na przykład if w Jade słowa kluczowego unless:

(n != 1),

wtedy powinieneś użyć

- var n = Math.round(Math.random() * 1) + 1 unless n == 1 script alert('Wygrałeś!');

Użycie poleceń case w Jade Jade obsługuje również inną niż w JavaScript formę konstrukcji warunkowej podobnej do switch: polecenie case. Wymienione polecenie pozwala na wskazanie danych wyjściowych na podstawie różnych scenariuszy. Poniższy przykład szablonu pokazuje, jak polecenie case można wykorzystać do wyświetlenia na trzy różne sposoby wyników operacji wyszukiwania w blogu. Jeżeli operacja nic nie znajdzie, użytkownikowi jest wyświetlany odpowiedni komunikat. W przypadku znalezienia pojedynczego wpisu bloga zostanie on wyświetlony. Jeżeli znalezionych będzie więcej wpisów, polecenie each zostanie użyte do iteracji przez posty i wyświetlenia ich tytułów. case results.length when 0 p Nie znaleziono szukanego wyrażenia. when 1 p= results[0].content default each result in results p= result.title

11.4.3. Organizacja szablonów Jade Po zdefiniowaniu szablonów warto wiedzieć, jak można je organizować. Podobnie jak w przypadku logiki aplikacji nie chcesz, aby pliki szablonów osiągnęły wielkie rozmiary. Pod względem koncepcyjnym pojedynczy plik szablonu powinien odpowiadać blokowi budulcowemu, na przykład stronie, paskowi bocznemu lub treści postu bloga.

W tym punkcie poznasz kilka mechanizmów pozwalających na współpracę różnych plików szablonów w celu wygenerowania treści. Są to: Strukturyzacja wielu szablonów za pomocą dziedziczenia szablonów. Implementacja układu za pomocą poprzedzania blokiem lub dołączania bloku. Dołączanie szablonów. Wielokrotne użycie logiki szablonu za pomocą poleceń mixin. Rozpoczynamy od zapoznania Cię z dziedziczeniem szablonów w Jade.

Strukturyzacja wielu szablonów za pomocą ich dziedziczenia Dziedziczenie szablonu to jeden ze sposobów strukturyzacji wielu szablonów. W tej koncepcji szablony są traktowane jak klasy w programowaniu zorientowanym obiektowo. Jeden szablon może rozszerzać inny, który z kolei będzie rozszerzał kolejny. Można użyć dowolnej liczby poziomów dziedziczenia, o ile ma to sens. Poniżej przedstawiono prosty przykład dziedziczenia szablonu w celu dostarczenia podstawowego opakowania HTML, które można wykorzystać jako opakowanie na treść strony. W katalogu roboczym utwórz nowy podkatalog o nazwie template przeznaczony na plik szablonu Jade. Szablon strony będzie umieszczony w pliku o nazwie layout.jade zawierającym następujący kod: html head block title body block content

Szablon layout.jade zawiera podstawową definicję strony HTML oraz dwa bloki. Wspomniane bloki są podczas dziedziczenia szablonów używane do zdefiniowania miejsca, w którym szablon dziedziczący powinien umieścić treść. W layout.jade mamy blok title pozwalający szablonowi dziedziczącemu na ustawienie tytułu oraz blok content pozwalający na wyświetlenie treści strony. Następnie w katalogu szablonów utwórz plik o nazwie page.jade przeznaczony do wypełniania bloków title i content: extends layout block title title Messages block content

each message in messages p= message

Do aplikacji dodaj logikę przedstawioną w listingu 11.12 (to zmodyfikowana wersja wcześniejszego przykładu zaprezentowanego w tym podrozdziale), która wyświetla wynik przetworzenia szablonu, pokazując tym samym dziedziczenie w akcji. Listing 11.12. Dziedziczenie szablonów w akcji var jade = require('jade'); var fs = require('fs'); var templateFile = './template/page.jade'; var iterTemplate = fs.readFileSync(templateFile); var context = { messages: [ 'Logowanie zakończone powodzeniem.', 'Witamy ponownie!' ]}; var iterFn = jade.compile( iterTemplate, {filename: templateFile} ); console.log(iterFn(context));

Teraz przechodzimy do innej funkcji poprzedzenia blokiem lub dołączenia bloku.

dziedziczenia

szablonów,

czyli

Implementacja układu za pomocą poprzedzenia blokiem lub dołączenia bloku W poprzednim przykładzie bloki w szablonie layout.jade nie zawierały treści, co powodowało, że ustawienie treści w szablonie page.jade było stosunkowo proste. Jeśli jednak blok w szablonie dziedziczącym zawiera treść, wówczas będzie ona uwzględniona, a nie zastąpiona przez szablon dziedziczący. Odbywa się to przez poprzedzenie blokiem lub dołączenie bloku. W ten sposób można zdefiniować stałą treść i dodawać do niej nową, zamiast całkowicie zastępować ją nową treścią. Przedstawiony poniżej szablon layout.jade zawiera dodatkowy blok o nazwie scripts, w którym znajduje się stała treść — znacznik script wczytujący popularną bibliotekę JavaScript jQuery. html head block title block scripts

script(src='//ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.js') body block content

Jeżeli chcesz, aby szablon page.jade dodatkowo wczytywał bibliotekę jQuery UI, możesz to osiągnąć, używając go w sposób przedstawiony w listingu 11.13. Listing 11.13. Użycie block append w celu wczytania dodatkowego pliku JavaScript extends layout Ten szablon rozszerza szablon layout. baseUrl = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/" block title title Messages block style Zdefiniowanie bloku style. link(rel="stylesheet", href= baseUrl+"themes/flick/jquery-ui.css") block append scripts Dołączenie tego bloku script do zdefiniowanego w szablonie layout. script(src= baseUrl+"jquery-ui.js") block content count = 0 each message in messages - count = count + 1 script $(function() { $("#message_#{count}").dialog({ height: 140, modal: true }); }); != '' + message + ''

Dziedziczenie szablonów to nie jedyny sposób na integrację wielu szablonów. Istnieje również możliwość użycia polecenia Jade o nazwie include.

Dołączanie szablonu Inne narzędzie przeznaczone do organizacji szablonów Jade to polecenie include. Wymienione polecenie powoduje dołączenie zawartości innego szablonu. Jeżeli w użytym we wcześniejszym przykładzie szablonie layout.jade umieścisz wiersz include footer, to otrzymasz następujący szablon: html head block title

block style block scripts script(src='//ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.js') body block content include footer

Jak zilustrowano na rysunku 11.5, podczas generowania danych wyjściowych szablonu layout.jade dołączona będzie również zawartość szablonu o nazwie footer.jade.

Rysunek 11.5. Oferowane przez Jade polecenie include pozwala na łatwe umieszczanie zawartości jednego szablonu w innym

Takie rozwiązanie można zastosować na przykład w celu dodania informacji o witrynie lub zaprojektowania elementów dla layout.jade. Istnieje również możliwość dołączania plików innych niż szablony Jade, wystarczy tylko podać rozszerzenie pliku (na przykład include twitter_widget.html).

Wielokrotne użycie logiki szablonu za pomocą polecenia mixin Wprawdzie oferowane przez Jade polecenie include jest odpowiednie do dołączania wcześniej utworzonych fragmentów kodu, ale nie będzie idealnym rozwiązaniem podczas tworzenia biblioteki wielokrotnego użytku funkcji, które można wykorzystać na różnych stronach lub w oddzielnych aplikacjach. Do tego celu Jade udostępnia polecenie mixin, które pozwala na zdefiniowanie wielokrotnego użycia fragmentów kodu Jade. Polecenie

mixin

jest analogiczne do funkcji w JavaScript. Podobnie jak funkcja

polecenie mixin może pobierać argumenty, które z kolei można wykorzystać do wygenerowania kodu Jade. Przyjmujemy założenie, że aplikacja obsługuje strukturę danych podobną do przedstawionej poniżej: var students = [ {name: 'Jan Kowalski', age: 23}, {name: 'Kasia Nowak', age: 25}, {name: 'Bartek Malinowski', age: 37} ];

Jeżeli chcesz zdefiniować rozwiązanie pozwalające na wygenerowanie listy HTML na podstawie danej właściwości każdego obiektu, wtedy możesz przygotować następujące polecenie mixin: mixin list_object_property(objects, property) ul each object in objects li= object[property]

Następnie wystarczy użyć polecenia użyj poniższego wiersza kodu Jade:

mixin

do wyświetlenia danych. Do tego celu

mixin list_object_property(students, 'name')

Dzięki wykorzystaniu dziedziczenia szablonów i poleceń include i mixin można bardzo łatwo wielokrotnie używać znaczników prezentacyjnych, a tym samym nie pozwolić na nadmierne zwiększenie plików szablonów.

11.5. Podsumowanie W ten sposób poznałeś trzy popularne silniki szablonów HTML. Możesz więc wykorzystać wybraną technikę szablonów do organizacji logiki aplikacji i warstwy prezentacyjnej. Społeczność Node opracowała wiele silników szablonów. Jeżeli żaden z trzech omówionych w rozdziale nie odpowiada Ci z jakiegokolwiek powodu, zawsze możesz wypróbować inne: https://www.npmjs.org/browse/keyword/template. Na przykład silnik szablonów Handlebars.js (https://github.com/wycats/handlebars.js/) rozszerza Mustache i dodaje kolejne funkcje, takie jak znaczniki warunkowe i globalnie dostępne funkcje lambda. Dustjs (https://github.com/akdubya/dustjs) stawia na wydajność i funkcje, na przykład strumieniowanie. Listę silników szablonów dla Node znajdziesz w projekcie consolidate.js (https://github.com/visionmedia/consolidate.js), który zapewnia API pozwalające na abstrakcję użycia silników szablonów i ułatwia

stosowanie wielu silników w aplikacji. Jeśli jednak myśl o nauce składni stosowanej w różnych szablonach jest dla Ciebie zniechęcająca, silnik o nazwie Plates (https://github.com/flatiron/plates) pozwoli Ci na pozostanie przy kodzie HTML i wykorzystanie logiki silnika do mapowania danych aplikacji na identyfikatory i klasy CSS w kodzie znaczników. Jeżeli sposób, w jaki Jade obsługuje rozdzielenie warstwy prezentacyjnej i logiki aplikacji, wydaje Ci się kuszący, zainteresuj się Stylusem (https://github.com/LearnBoost/stylus), czyli projektem stosującym podobne podejście podczas tworzenia stylów CSS. Zapoznałeś się z wiedzą niezbędną do tworzenia profesjonalnych aplikacji sieciowych. W następnym rozdziale zajmiemy się wdrażaniem, czyli udostępnieniem aplikacji całemu światu.

Część III Co dalej? W ostatniej części książki przeczytasz, jak można używać Node w inny sposób niż do tworzenia aplikacji sieciowych oraz jak za pomocą biblioteki Socket.IO dodawać do aplikacji sieciowej komponenty działające w czasie rzeczywistym. Ponadto dowiesz się, jak wykorzystywać Node do tworzenia serwerów innych niż HTTP TCP/IP, a nawet w narzędziach działających z poziomu powłoki. Oprócz nowych sposobów użycia Node poznasz również funkcjonowanie ekosystemu społeczności Node, odkryjesz miejsca, w których można uzyskać pomoc, a także dowiesz się, jak własnymi projektami podzielić się ze społecznością Node za pomocą repozytorium Node Package Manager.

Rozdział 12. Wdrażanie aplikacji Node i zapewnienie bezawaryjnego działania W tym rozdziale: • Wybór hostingu dla aplikacji Node. • Wdrażanie typowej aplikacji. • Zapewnienie działania aplikacji i maksymalizacja jej wydajności.

Opracowanie aplikacji sieciowej to jedno, a umieszczenie jej w środowisku produkcyjnym to zupełnie co innego. W przypadku każdej platformy sieciowej istnieją pewne wskazówki i sztuczki pomagające w zwiększeniu stabilności i maksymalizacji wydajności. Node nie jest tutaj wyjątkiem. Kiedy stajesz przed koniecznością wdrożenia aplikacji sieciowej, w pierwszej kolejności zastanawiasz się, jakie masz możliwości w zakresie hostingu. Konieczne jest również rozważenie, jak monitorować aplikację i zapewnić jej działanie. Być może zastanawiasz się, co można jeszcze zrobić, aby aplikacja działała z maksymalną szybkością. W tym rozdziale dowiesz się, jak przeprowadzić wdrożenie aplikacji sieciowej Node. Na początek sprawdzimy, jakie mamy możliwości w zakresie hostingu aplikacji Node.

12.1. Hosting aplikacji Node Większość programistów aplikacji sieciowych zna aplikacje oparte na PHP. Kiedy serwer Apache zapewniający obsługę PHP otrzyma żądanie HTTP, ścieżkę żądanego adresu URL mapuje na konkretny plik, a PHP wykonuje zawartość wspomnianego pliku. Taka funkcjonalność ułatwia wdrażanie aplikacji PHP — pliki PHP wystarczy umieścić w określonej lokalizacji systemu plików, a staną się dostępne dla przeglądarek internetowych. Cechą hostingu PHP jest nie tylko łatwe wdrożenie, ale również niska cena, ponieważ serwery są bardzo często współdzielone przez wielu użytkowników. Wdrożenie aplikacji Node za pomocą usług hostingu w chmurze przygotowanych pod kątem Node i oferowanych przez firmy takie jak Joyent, Heroku, Nodejitsu, VMware i Microsoft nie jest trudne. Wspomniane usługi hostingu w chmurze przygotowane z myślą o Node są warte uwagi, jeśli chcesz uniknąć problemów związanych z administracją własnym serwerem, a jednocześnie chcesz korzystać z zalet diagnostyki opracowanej specjalnie dla Node. Przykładem

może być oferowana przez system operacyjny SmartOS firmy Joyent możliwość sprawdzenia, która logika w aplikacji Node działa najwolniej. Witryna Cloud9 została zbudowana za pomocą Node.js, a nawet oferuje oparte na przeglądarce internetowej zintegrowane środowisko programistyczne (IDE), w którym można klonować projekty z serwisu GitHub, pracować nad nimi w przeglądarce internetowej, a następnie wdrażać do wielu usług hostingu w chmurze przygotowanych specjalnie dla Node (patrz tabela 12.1). Tabela 12.1. Usługi IDE i hostingu w chmurze przygotowane pod kątem Node Nazwa

Witryna internetowa

Heroku

https://www.heroku.com/

Nodejitsu

https://www.nodejitsu.com/

VMware’s Cloud Foundry

http://www.gopivotal.com/platform-as-a-service/pivotal-cf

Microsoft Azure SDK for Node.js

http://azure.microsoft.com/en-us/develop/nodejs/

Cloud9 IDE

https://c9.io/

Alternatywą dla hostingu w chmurze przygotowanego z myślą o Node jest uruchomienie własnego serwera. Linux to popularny wybór dla serwerów Node. Oferuje znacznie większą elastyczność niż wspomniany hosting w chmurze przygotowany specjalnie dla Node, ponieważ pozwala na łatwą instalację potrzebnych aplikacji, na przykład baz danych. W przypadku hostingu w chmurze do dyspozycji jest na ogół ograniczona liczba aplikacji. Administrowanie serwerem działającym pod kontrolą systemu Linux wymaga doświadczenia. Jeżeli zdecydujesz się na użycie własnego serwera, będziesz musiał nieco poczytać o wybranej dystrybucji systemu Linux i upewnić się o posiadaniu odpowiedniej wiedzy z zakresu jego konfiguracji i obsługi. VirtualBox. Jeżeli zagadnienia związane z administracją serwerem są dla Ciebie nowością, zawsze możesz eksperymentować za pomocą oprogramowania wirtualizacji, na przykład takiego jak VirtualBox (https://www.virtualbox.org/), które pozwala na uruchamianie komputerów wirtualnych działających pod kontrolą danego systemu, na przykład Linux. W przypadku użycia wirtualizacji nie ma żadnego znaczenia, pod kontrolą jakiego systemu działa fizyczny komputer. Jeżeli temat różnych opcji serwera nie jest Ci obcy, możesz jedynie przejrzeć informacje aż do podrozdziału 12.2, w którym przystąpimy do omawiania podstaw wdrażania aplikacji Node. Najpierw przekonajmy się, jakie mamy dostępne opcje: serwery dedykowane,

serwery VPS, ogólnego przeznaczenia serwery chmury. Przeanalizujemy teraz niektóre opcje dostępne podczas wyboru hostingu dla aplikacji Node.

12.1.1. Serwery dedykowane i VPS Serwer może być fizyczny i wówczas jest określany mianem dedykowanego lub też wirtualny. Serwery wirtualne działają wewnątrz fizycznych i współdzielą zasoby serwera fizycznego: procesor, pamięć RAM i przestrzeń na dysku twardym. Serwery wirtualne emulują fizyczne i można nimi administrować w dokładnie taki sam sposób. W pojedynczym serwerze fizycznym może działać wiele serwerów wirtualnych. Serwery dedykowane są zwykle dużo droższe od wirtualnych, a ich przygotowanie najczęściej wymaga nieco dłuższego czasu, ponieważ może wystąpić konieczność zamówienia komponentów, złożenia całości i przeprowadzenia konfiguracji. Natomiast serwery VPS (ang. Virtual Private Server) mogą być przygotowane bardzo szybko, ponieważ są tworzone w istniejących serwerach fizycznych. VPS to dobre rozwiązanie w zakresie hostingu dla aplikacji sieciowych, jeśli nie przewidujesz szybkiego wzrostu stopnia ich użycia. Rozwiązanie oparte na VPS jest tanie, a ponadto pozwala na łatwe dodanie zasobów, takich jak przestrzeń na dysku lub pamięć RAM, gdy wystąpi potrzeba. Odpowiednia technologia jest już opracowana, a wiele firm, na przykład Linode (https://www.linode.com/) i Prgmr (http://prgmr.com/xen/), bardzo ułatwia rozpoczęcie pracy z VPS. Podobnie jak serwery wirtualne, także serwery VPS zwykle nie mogą być tworzone na żądanie. Najczęściej nie nadążają za szybko zwiększającym się poziomem zużycia zasobów, ponieważ to wymaga możliwości szybkiego dodawania kolejnych serwerów bez konieczności interwencji ze strony człowieka. Aby spełnić wspomniane wymagania, trzeba skorzystać z hostingu w chmurze.

12.1.2. Hosting w chmurze Serwer chmury jest podobny do VPS pod tym względem, że stanowi wirtualną emulację serwera dedykowanego. Jednak w porównaniu z serwerami dedykowanymi i VPS ma przewagę w postaci w pełni zautomatyzowanego zarządzania. Serwery chmury mogą być tworzone, uruchamiane, zatrzymywane i usuwane za pomocą zdalnego interfejsu lub API.

Kto będzie potrzebował tego rodzaju rozwiązania? Przypuśćmy, że założyłeś firmę posiadającą oparte na Node korporacyjne oprogramowanie intranet. Chcesz umożliwić klientom zarejestrowanie się do oferowanej usługi i krótko po tym uzyskanie dostępu do ich serwerów działających w Twoim oprogramowaniu. Wprawdzie możesz zatrudnić personel techniczny do konfiguracji i wdrażania serwerów klientów przez całą dobę, ale jeśli nie posiadasz własnego centrum danych, wspomniany personel nadal będzie musiał koordynować działania z dostawcą serwerów dedykowanych lub VPS, aby na czas zapewnić odpowiednie zasoby. Dzięki użyciu serwerów chmury operacje zarządzania serwerem można przeprowadzać za pomocą instrukcji wysyłanych przez API do dostawcy chmury i tym samym uzyskiwać dostęp do nowych serwerów, gdy zajdzie potrzeba. Ten poziom automatyzacji pozwala na szybkie dostarczanie usług klientom bez konieczności podejmowania interwencji ze strony człowieka. Na rysunku 12.1 zilustrowano przykład użycia hostingu w chmurze do tworzenia i usuwania serwerów aplikacji.

Rysunek 12.1. Tworzenie, uruchamianie, zatrzymywanie i usuwanie serwerów w chmurze może być w pełni zautomatyzowane

Wadą użycia serwerów w chmurze jest ich wyższa cena niż w przypadku VPS, a także konieczność posiadania pewnej wiedzy w zakresie konkretnej platformy w chmurze.

Amazon Web Services Najstarszą i najpopularniejszą platformą chmury jest Amazon Web Services (http://aws.amazon.com/). AWS składa się z różnych usług związanych z hostingiem, na przykład dostarczania wiadomości e-mail, CDN (ang. Content Delivery Networks) itd. Oferowana przez Amazon usługa Elastic Compute Cloud (EC2) to jedna z centralnych usług AWS, pozwalająca na tworzenie serwerów w chmurze, gdy zachodzi potrzeba. Wirtualne serwery EC2 są nazywane egzemplarzami i mogą być zarządzane z poziomu powłoki lub za pomocą konsoli opartej na przeglądarce internetowej, jak pokazano na rysunku 12.2. Ponieważ poznanie sposobu zarządzania AWS z poziomu powłoki wymaga nieco czasu, użytkownikom dopiero zaczynającym pracę z EC2 zaleca się użycie konsoli graficznej.

Rysunek 12.2. Konsola AWS jest przeznaczona do zarządzania serwerami chmury Amazon; dla nowych użytkowników to narzędzie łatwiejsze w użyciu niż powłoka

Na szczęście z powodu rozpowszechnienia AWS bardzo łatwo można znaleźć w internecie pomoc oraz samouczki dotyczące tej konsoli. Przykładem może być oferowany przez Amazon samouczek „Getting Started with Amazon EC2 Linux Instances” (http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html).

Rackspace Cloud Znacznie prostszą i łatwiejszą w użyciu platformą chmury jest Rackspace Cloud

(http://www.rackspace.com/cloud/). Wspomniana łatwość może wydawać się kusząca, ale Rackspace Cloud oferuje znacznie mniejszą gamę funkcji i produktów powiązanych z chmurą niż AWS, a interfejs graficzny przeznaczony do zarządzania usługą jest, delikatnie mówiąc, niewygodny. Serwerami Rackspace Cloud można zarządzać za pomocą interfejsu graficznego lub utworzonych przez społeczność narzędzi działających z poziomu powłoki. W tabeli 12.2 podsumowano omówione w tym podrozdziale opcje, które znajdziesz w zakresie hostingu aplikacji Node. Po ogólnym przedstawieniu usług, które można wykorzystać do hostingu aplikacji Node, teraz przechodzimy do tematu faktycznego wdrożenia aplikacji Node w serwerze. Tabela 12.2. Podsumowanie opcji dotyczących hostingu Możliwość wzrostu ruchu sieciowego

Opcja hostingu

Względny koszt

Wolna

serwer dedykowany

średni

Liniowa

serwer VPS

mały

Nieprzewidywalna

chmura

wysoki

12.2. Podstawy wdrożenia Przyjmujemy założenie, że utworzyłeś aplikację sieciową, którą chcesz się pochwalić światu. Ewentualnie opracowałeś aplikację komercyjną i musisz ją przetestować przed umieszczeniem w środowisku produkcyjnym. Prawdopodobnie zaczniesz od prostego wdrożenia, a następnie wykonasz nieco dodatkowej pracy w celu maksymalizacji wydajności i zapewnienia niezawodnego działania aplikacji. W tym podrozdziale przeanalizujemy proste, tymczasowe wdrożenie Git, a także omówimy sposoby zapewnienia niezawodnego działania aplikacji za pomocą Forever. Tymczasowe wdrożenie istnieje tylko do chwili ponownego uruchomienia, ale jednocześnie charakteryzuje się szybką konfiguracją.

12.2.1. Wdrożenie z repozytorium Git Poniżej przedstawiono podstawowe wdrożenie za pomocą repozytorium Git, co pozwoli Ci na poznanie podstawowych etapów procesu. Wdrożenie odbywa się przez wykonanie wymienionych poniżej kroków: 1. Nawiązanie połączenia z serwerem za pomocą SSH. 2. Instalacja Node i narzędzi kontroli wersji (na przykład Git lub Subversion) w serwerze, o ile zachodzi potrzeba.

3. Pobranie z repozytorium kontroli wersji plików aplikacji, między innymi skryptów Node, obrazów, arkuszy stylów CSS, a następnie umieszczenie ich w serwerze. 4. Uruchomienie aplikacji. Poniżej znajduje się przykład uruchomienia aplikacji po pobraniu jej plików z repozytorium Git: git clone https://github.com/Marak/hellonode.git cd hellonode node server.js

Podobnie jak PHP, także Node nie działa jako zadanie w tle. Dlatego też przedstawione tutaj podstawowe wdrożenie wymaga zachowania otwartego połączenia SSH. Po zamknięciu połączenia nastąpi natychmiastowe zakończenie działania aplikacji. Na szczęście bardzo łatwo można zapewnić nieustanne działanie aplikacji za pomocą prostego narzędzia. Wdrożenie automatyczne. Wdrożenie aplikacji Node można zautomatyzować na wiele sposobów. Jednym z nich jest użycie narzędzia takiego jak Fleet (https://github.com/substack/fleet), które pozwala na wdrożenie jednego lub więcej serwerów za pomocą git push. Znacznie bardziej tradycyjnym podejściem jest użycie Capistrano, co zostało dokładnie omówione w poście Evana Tahlera zatytułowanym „Deploying node.js applications with Capistrano” (http://blog.evantahler.com/blog/deployingnode-js-applications-with-capistrano.html).

12.2.2. Zapewnienie działania aplikacji Node Przyjmujemy założenie, że utworzyłeś osobisty blog za pomocą aplikacji Cloud9 Nog (https://github.com/c9/nog), a teraz chcesz ją wdrożyć i mieć gwarancje jej działania nawet po zamknięciu połączenia SSH. W społeczności Node najpopularniejszym narzędziem do tego celu jest Nodejitsu Forever (https://github.com/nodejitsu/forever). Zapewnia ono działanie aplikacji po zamknięciu połączenia SSH, a ponadto ponownie ją uruchamia, jeśli uległa awarii. Koncepcję sposobu działania Forever zilustrowano na rysunku 12.3.

Rysunek 12.3. Narzędzie Forever pomaga w zapewnieniu działania aplikacji, nawet jeśli ulegnie ona awarii

Forever można zainstalować globalnie za pomocą polecenia

sudo.

Polecenie sudo. Bardzo często podczas instalacji globalnej (to znaczy z użyciem opcji -g) modułu menedżera npm polecenie npm trzeba poprzedzić p ol ecen i em sudo (http://www.sudo.ws/), aby menedżer npm działał z uprawnieniami superużytkownika. Po pierwszym wydaniu polecenia sudo konieczne jest podanie hasła użytkownika. Następnie wykonane zostanie polecenie znajdujące się po sudo. Poniższe polecenie powoduje globalną instalację Forever: sudo npm install -g forever

Po instalacji możesz użyć narzędzia Forever do uruchomienia bloga i zapewnienia jego działania. W tym celu wystarczy wydać poniższe polecenie: forever start server.js

Jeżeli z jakiegokolwiek powodu będziesz chciał zatrzymać działanie aplikacji bloga, wystarczy użyć polecenia stop narzędzia Forever: forever stop server.js

Forever pozwala również na pobranie listy aplikacji zarządzanych przez to narzędzie. Do tego celu służy polecenie list: forever list

Inna użyteczna możliwość narzędzia Forever to opcjonalne ponowne uruchomienie aplikacji po zmianie któregokolwiek z jej plików źródłowych. To uwalnia programistę od konieczności ręcznego ponownego uruchamiania aplikacji za każdym razem, gdy zostanie dodana nowa funkcja lub usunięty błąd. Aby uruchomić narzędzie Forever we wspomnianym trybie, należy użyć opcji -w: forever -w start server.js

Wprawdzie Forever to niezwykle użyteczne narzędzie przeznaczone do

wdrażania aplikacji, ale na potrzeby długoterminowych wdrożeń możesz potrzebować narzędzia wyposażonego w nieco więcej funkcji i możliwości. W kolejnym podrozdziale poznasz wybrane przemysłowe rozwiązania w zakresie monitorowania oraz dowiesz się, jak można zmaksymalizować wydajność działania aplikacji.

12.3. Maksymalizacja wydajności i czasu bezawaryjnego działania aplikacji Kiedy aplikacja Node jest gotowa do udostępnienia użytkownikom, na pewno chcesz się upewnić o jej uruchamianiu i zatrzymywaniu wraz z serwerem oraz o automatycznym ponownym uruchamianiu jej po awarii serwera. Bardzo łatwo zapomnieć o zatrzymaniu aplikacji przed ponownym uruchomieniem serwera lub o jej uruchomieniu tuż po ponownym uruchomieniu serwera. Konieczne jest również upewnienie się o podjęciu wszelkich kroków zapewniających maksymalną wydajność działania aplikacji. Jeśli aplikacja działa w serwerze wyposażonym w procesor czterordzeniowy, wtedy sensowne jest wykorzystanie przez nią więcej niż tylko jednego rdzenia. Gdy używany jest tylko jeden rdzeń, a ilość ruchu sieciowego obsługiwanego przez aplikację gwałtownie wzrośnie, wówczas jeden rdzeń może nie mieć wystarczających możliwości do obsługi całego ruchu, a aplikacja nie będzie natychmiast odpowiadała na działania użytkownika. Oprócz wykorzystania wszystkich rdzeni procesora należy też unikać użycia Node do udostępniania plików statycznych w witrynach produkcyjnych obsługujących duży ruch sieciowy. Node opracowano dla aplikacji interaktywnych, takich jak aplikacje sieciowe i protokoły TCP/IP. Dlatego też nie potrafi udostępniać plików statycznych w tak efektywny sposób jak oprogramowanie przeznaczone tylko do tego celu. Do udostępniania plików statycznych należy wykorzystać technologie, które się w tym specjalizują, takie jak Nginx (http://nginx.org/en/). Ewentualnie wszystkie pliki statyczne można umieścić w systemie CDN, na przykład Amazon S3 (http://aws.amazon.com/s3/), a następnie odwoływać się do tych plików z poziomu aplikacji Node. W tym podrozdziale zostaną przedstawione pewne funkcje pomagające w poprawie wydajności i zapewnieniu niezawodnego działania aplikacji: Użycie Upstart do zapewnienia działania aplikacji i jej ponownego uruchamiania, na przykład po awarii. Użycie API klastra Node do wykorzystania możliwości oferowanych przez procesory wielordzeniowe. Obsługa plików statycznych w aplikacji Node za pomocą Nginx.

Rozpoczynamy od oferującego potężne możliwości i jednocześnie łatwego w użyciu narzędzia Upstart, które zapewnia niezawodne działanie aplikacji.

12.3.1. Zapewnienie działania aplikacji za pomocą Upstart Przyjmujemy założenie, że jesteś zadowolony z opracowanej aplikacji i chcesz ją udostępnić innym. Ponadto chcesz mieć absolutną pewność, że po ponownym uruchomieniu serwera nie zapomnisz ponownie uruchomić aplikacji. Poza tym po ewentualnej awarii aplikacja powinna zostać automatycznie ponownie uruchomiona, a awaria — odnotowana w dzienniku zdarzeń. Powinieneś też otrzymać informację o incydencie, co pozwoli Ci na zdiagnozowanie i usunięcie problemów. Upstart (http://upstart.ubuntu.com/) to projekt zapewniający eleganckie rozwiązanie w zakresie zarządzania uruchamianiem i zatrzymywaniem dowolnej aplikacji systemu Linux, w tym także aplikacji Node. Najnowsze wydania dystrybucji Ubuntu i CentOS obsługują użycie Upstart. Jeżeli narzędzie Upstart nie jest jeszcze zainstalowane w Ubuntu, instalacja zostanie przeprowadzona po wydaniu poniższego polecenia: sudo apt-get install upstart

Jeżeli narzędzie Upstart nie jest jeszcze zainstalowane w systemie CentOS, instalacja zostanie przeprowadzona po wydaniu poniższego polecenia: sudo yum install upstart

Po zainstalowaniu Upstart konieczne jest dodanie pliku konfiguracyjnego Upstart dla każdej aplikacji. Wspomniane pliki są tworzone w katalogu /etc/init i powinny mieć nazwę w stylu nazwa_aplikacji.conf. Pliki konfiguracyjne nie wymagają uprawnień do ich wykonywania. Poniższe polecenie powoduje utworzenie pustego pliku konfiguracyjnego Upstart dla przykładowej aplikacji przedstawionej w tym rozdziale: sudo touch /etc/init/hellonode.conf

W utworzonym pliku konfiguracyjnym umieść kod przedstawiony w listingu 12.1. Przygotowana konfiguracja spowoduje uruchomienie aplikacji wraz z serwerem i zakończenie jej działania wraz z zamknięciem serwera. Sekcja exec to kod wykonywany przez Upstart. Listing 12.1. Typowy plik konfiguracyjny Upstart author "Jak Kowalski" Podanie imienia i nazwiska twórcy aplikacji. description "hellonode" Podanie nazwy aplikacji lub jej opisu. setuid "nonrootuser" Aplikacja będzie uruchomiona przez użytkownika innego niż

root. start on (local-filesystems and net-device-up IFACE=eth0) Uruchomienie aplikacji podczas startu systemu, gdy system plików i sieć będą już dostępne. stop on shutdown Zakończenie działania aplikacji podczas zamykania systemu. respawn Ponowne uruchomienie aplikacji po jej awarii. console log Komunikaty standardowego wejścia i błędów będą zapisywane w pliku /var/log/upstart/nazwa_aplikacji.log. env NODE_ENV=production Zdefiniowanie wszelkich zmiennych środowiskowych niezbędnych dla aplikacji. exec /usr/bin/node /ścieżka/dostępu/do/serwera.js Podanie polecenia uruchamiającego aplikację.

Przedstawiony plik konfiguracyjny zapewnia działanie procesu po ponownym uruchomieniu serwera, a nawet po wystąpieniu awarii samej aplikacji. Wszystkie dane wyjściowe wygenerowane przez aplikację zostaną umieszczone w pliku /var/log/upstart/hellonode.log, a narzędzie Upstart automatycznie zajmie się rotacją dzienników zdarzeń. Po utworzeniu pliku konfiguracyjnego Upstart aplikację można uruchomić przez wydanie poniższego polecenia: sudo service hellonode

Jeżeli uruchomienie aplikacji zakończyło się powodzeniem, otrzymasz komunikat podobny do poniższego: hellonode start/running, process 6770

Narzędzie Upstart oferuje bogate możliwości w zakresie konfiguracji. Warto zapoznać się z dostępnym w internecie podręcznikiem (http://upstart.ubuntu.com/cookbook/), w którym wymieniono wszystkie opcje. Narzędzie Upstart i ponowne uruchamianie Po użyciu opcji respawn narzędzie Upstart będzie nieustannie i automatycznie uruchamiać aplikację po jej awarii, o ile aplikacja nie ulegnie awarii dziesięciokrotnie w ciągu pięciu sekund. Wspomniany limit można zmienić za pomocą opcji respawn limit liczba przedział_czasu, gdzie liczba oznacza ilość uruchomień w podanym przedziale_czasu wyrażonym w sekundach. Na przykład ustawienie limitu dwudziestu uruchomień w ciągu pięciu sekund wymaga użycia poniższych opcji: respawn respawn limit 20 5 Jeżeli aplikacja zostanie ponownie uruchomiona 10 razy w ciągu 5 sekund (domyślny limit), zwykle oznacza to problem w kodzie lub konfiguracji i prawdopodobnie nigdy nie zostanie ona prawidłowo uruchomiona. Po osiągnięciu limitu narzędzie Upstart nie będzie ponownie próbowało uruchomić aplikacji, co ma na celu zachowanie zasobów dla innych procesów. Dobrym rozwiązaniem jest sprawdzanie stanu aplikacji poza narzędziem Upstart, aby jej programistom dostarczać odpowiednie komunikaty, na przykład w postaci wiadomości e-mail. Operacja może polegać na przejściu do witryny i sprawdzeniu, czy użytkownik otrzymuje prawidłową odpowiedź. Możesz wykorzystać własne metody lub użyć jednego z dostępnych narzędzi przeznaczonych do tego celu, na przykład Monit (http://mmonit.com/monit/) lub Zabbix (http://www.zabbix.com/).

Skoro już wiesz, jak zapewnić działanie aplikacji niezależnie od awarii lub ponownego uruchomienia serwera, kolejnym logicznym krokiem jest osiągnięcie maksymalnej wydajności działania. Na tym polu pomocne może okazać się API klastra Node.

12.3.2. API klastra — wykorzystanie zalety w postaci wielu rdzeni Większość nowoczesnych procesorów zawiera kilka rdzeni, ale Node podczas działania używa tylko jednego z nich. Jeżeli umieściłeś aplikację Node w serwerze i chcesz w maksymalnym stopniu wykorzystać jej możliwości sprzętowe, jednym z rozwiązań może być ręczne uruchomienie wielu egzemplarzy aplikacji działających na różnych portach TCP/IP. Następnie za pomocą mechanizmu równoważenia obciążenia trzeba rozkładać ruch sieciowy między poszczególne egzemplarze. Przygotowanie tego rodzaju rozwiązania jest pracochłonne. Aby ułatwić wykorzystanie wielu rdzeni przez pojedynczą aplikację, do Node dodano API klastra. Wymienione API ułatwia aplikacji jednoczesne działanie wielu „procesów roboczych” w poszczególnych rdzeniach, wykonujących to samo zadanie i udzielających odpowiedzi na tym samym porcie. Na rysunku 12.4 pokazano sposób działania aplikacji wykorzystującej API klastra w procesorze czterordzeniowym.

Rysunek 12.4. Utworzenie trzech dodatkowych procesów roboczych w procesorze czterordzeniowym

Kod przedstawiony w listingu 12.2 automatycznie tworzy procesy robocze dla każdego dodatkowego rdzenia w procesorze. Listing 12.2. Demonstracja użycia API klastra Node var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; Ustalenie liczby rdzeni w procesorze

serwera. if (cluster.isMaster) { for (var i = 0; i < numCPUs; i++) { Utworzenie procesu roboczego dla każdego z nich. cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('Proces roboczy ' + worker.process.pid + ' zakończył działanie.'); }); } else { http.Server(function(req, res) { Zdefiniowanie zadania wykonywanego przez poszczególne procesy robocze. res.writeHead(200); res.end('Jestem procesem roboczym działającym w procesie ' + process.pid); }).listen(8000); }

Ponieważ procesy główny i robocze to oddzielne procesy systemu operacyjnego (to konieczne, aby mogły działać w oddzielnych rdzeniach), nie mogą współdzielić informacji o stanie za pomocą zmiennych globalnych. Na szczęście API klastra Node zapewnia rozwiązanie pozwalające na komunikację między procesem głównym i roboczymi. W listingu 12.3 przedstawiono przykład aplikacji, w której zachodzi komunikacja między procesami głównym i roboczymi. Liczba wszystkich żądań jest przechowywana przez proces główny, natomiast poszczególne procesy robocze informują o obsłudze każdego żądania. Listing 12.3. Demonstracja użycia API klastra Node var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; var workers = {}; var requests = 0; if (cluster.isMaster) { for (var i = 0; i < numCPUs; i++) { workers[i] = cluster.fork(); (function (i) { workers[i].on('message', function(message) { Nasłuchiwanie informacji z procesu roboczego. if (message.cmd == 'incrementRequestTotal') { requests++; Zwiększenie liczby wszystkich żądań.

for (var j = 0; j < numCPUs; j++) { workers[j].send({ Wysłanie wszystkim procesom roboczym informacji o całkowitej liczbie żądań. cmd:

'updateOfRequestTotal',

requests: requests }); } } }); })(i); Użycie domknięcia w celu zachowania wartości procesu roboczego. } cluster.on('exit', function(worker, code, signal) { console.log('Proces roboczy ' + worker.process.pid + ' zakończył działanie.'); }); } else { process.on('message', function(message) { Nasłuchiwanie informacji z procesu głównego. if (message.cmd == 'updateOfRequestTotal') { requests = message.requests; Uaktualnienie licznika żądań za pomocą komunikatu procesu głównego. } }); http.Server(function(req, res) { res.writeHead(200); res.end('Proces roboczy w procesie ' + process.pid + ' twierdzi, że klaster udzielił odpowiedzi na ' + requests + ' żądań.'); process.send({cmd: 'incrementRequestTotal'}); Poinformowanie procesu głównego o konieczności zwiększenia licznika żądań. }).listen(8000); }

Użycie API klastra Node to prosty sposób pozwalający na tworzenie aplikacji wykorzystujących pełnię możliwości nowoczesnego sprzętu komputerowego.

12.3.3. Proxy i hosting plików statycznych Wprawdzie Node to efektywne rozwiązanie w zakresie udostępniania dynamicznej treści sieciowej, ale nie sprawdza się już tak efektywnie podczas udostępniania plików statycznych, takich jak obrazy, style CSS lub skrypty

JavaScript działające po stronie klienta. Udostępnianie plików statycznych przez HTTP to specjalny rodzaj zadania, do realizacji którego opracowano odpowiednie oprogramowanie. Wspomniane oprogramowanie jest od lat używane do tego rodzaju operacji i zostało specjalnie zoptymalizowane pod ich kątem. Na szczęście w Node bardzo łatwo można przeprowadzić konfigurację Nginx (http://nginx.org/en/) — to dostępny jako oprogramowanie open source serwer WWW, który został zoptymalizowany do udostępniania plików statycznych. W typowej konfiguracji Nginx/Node serwer Nginx początkowo obsługuje każde żądanie sieciowe i te żądania, które nie dotyczą plików statycznych, są przekazywane do Node. Tego rodzaju konfigurację zilustrowano na rysunku 12.5.

Rysunek 12.5. Nginx można użyć jako proxy do szybkiego przekazywania zasobów statycznych z powrotem do klientów sieciowych

Rozwiązanie pokazane na rysunku 12.5 zostało zaimplementowane w kodzie przedstawionym w listingu 12.4. Ten listing to sekcja http pliku konfiguracyjnego Nginx. Wspomniany plik konfiguracyjny Nginx jest w systemie Linux przechowywany zgodnie z konwencją w katalogu /etc (/etc/nginx/nginx.conf). Dzięki użyciu Nginx do obsługi statycznych zasobów aplikacji sieciowych gwarantujesz, że Node będzie wykorzystywane do zadań, w realizacji których sprawdza się najlepiej. Listing 12.4. Plik konfiguracyjny, który używa Nginx jako proxy dla Node.js i udostępnia pliki statyczne http { upstream my_node_app { server 127.0.0.1:8000; Adres IP i numer portu aplikacji Node.

} server { listen 80; Port, na którym proxy będzie otrzymywało żądania. server_name localhost domain.com; access_log /var/log/nginx/my_node_app.log; location ~ /static/ { Obsługa żądań plików dla adresów URL rozpoczynających się od /static/. root /home/node/my_node_app; if (!-f $request_filename) { return 404; } } location / { Zdefiniowanie ścieżki adresu URL, na który proxy będzie udzielać odpowiedzi. proxy_pass http://my_node_app; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; } } }

12.4. Podsumowanie W tym rozdziale przedstawiono wiele opcji w zakresie hostingu aplikacji Node, między innymi rozwiązania opracowane pod kątem Node, serwery dedykowane, serwery VPS oraz hosting w chmurze. Każda opcja sprawdza się w innych zastosowaniach. Gdy osiągniesz gotowość do wdrożenia aplikacji Node dla ograniczonego kręgu odbiorców, proces wdrożenia będziesz mógł przeprowadzić szybko za pomocą narzędzia Forever. Z kolei w długoterminowych wdrożeniach dobrym rozwiązaniem może być zautomatyzowanie operacji uruchamiania i zatrzymywania działania aplikacji za pomocą narzędzia Upstart. Abyś mógł w maksymalnym stopniu wykorzystać zasoby sprzętowe serwera, do dyspozycji masz API klastra Node pozwalające na jednoczesne uruchamianie egzemplarzy aplikacji w wielu rdzeniach. Jeżeli aplikacja sieciowa musi udostępniać zasoby statyczne, takie jak obrazy lub dokumenty PDF, wówczas warto skonfigurować serwer Nginx i zastosować go w charakterze proxy dla

aplikacji Node. W ten sposób poznałeś tajniki aplikacji sieciowych Node. Warto więc dowiedzieć się, co jeszcze można osiągnąć za pomocą Node. W kolejnym rozdziale przyjrzymy się innym zastosowaniom Node: począwszy od tworzenia narzędzi działających w powłoce aż po narzędzia pozwalające na pobieranie danych z witryn internetowych.

Rozdział 13. Nie tylko serwery WWW W tym rozdziale: • Użycie biblioteki Socket.IO do prowadzenia w czasie rzeczywistym komunikacji między przeglądarkami internetowymi. • Implementacja sieci TCP/IP. • Użycie API Node do współpracy z systemem operacyjnym. • Opracowanie narzędzi działających w powłoce i posługiwanie się nimi.

Asynchroniczna natura Node pozwala na wykonywanie intensywnych operacji wejścia--wyjścia, co mogłoby być niemożliwe lub nieefektywne w środowisku synchronicznym. W tej książce omówiono aplikacje HTTP i być może chcesz zapytać: a co z innymi rodzajami aplikacji? Do czego Node może być jeszcze użyteczne? Powinieneś wiedzieć, że framework Node nie jest przeznaczony jedynie dla HTTP, ale służy do wszelkiego rodzaju ogólnych zadań wymagających przeprowadzania operacji wejścia-wyjścia. Oznacza to, że za pomocą Node możesz utworzyć praktycznie dowolny rodzaj aplikacji, na przykład program działający na poziomie powłoki, skrypt przeznaczony do administracji systemem, a także działające w czasie rzeczywistym aplikacje sieciowe. W tym rozdziale dowiesz się, jak tworzyć działające w czasie rzeczywistym serwery WWW wykraczające poza tradycyjny model serwera HTTP. Ponadto poznasz także inne API Node, które można wykorzystać do tworzenia innych rodzajów aplikacji, takich jak serwery TCP lub programy powłoki. Rozpoczynamy od omówienia biblioteki Socket.IO pozwalającej na prowadzenie w czasie rzeczywistym komunikacji między przeglądarkami internetowymi i serwerem.

13.1. Biblioteka Socket.IO Biblioteka Socket.IO (http://socket.io/) to bez wątpienia najbardziej znany moduł w społeczności Node. Programiści zainteresowani tworzeniem aplikacji sieciowych działających w czasie rzeczywistym, którzy nie znają Node, wcześniej lub później napotykają Socket.IO, a następnie odkrywają Node. Biblioteka Socket.IO pozwala na tworzenie działających w czasie rzeczywistym aplikacji sieciowych opartych na dwukierunkowej komunikacji między serwerem i klientem. W najprostszej postaci biblioteka Socket.IO ma API bardzo podobne do WebSocket API ( http://www.websocket.org/) oraz kilka wbudowanych rozwiązań zapasowych dla starszych przeglądarek internetowych, które nie

obsługują nowych funkcji. Socket.IO zapewnia wygodne API przeznaczone do rozgłaszania, wysyłania wiadomości itd. Oferowane funkcje powodują, że Socket.IO to bardzo popularna biblioteka dla gier działających w przeglądarkach internetowych, aplikacji czatu i aplikacji wykorzystujących strumienie. HTTP to protokół bezstanowy, co oznacza, że klient może wykonać tylko pojedyncze, krótkie żądania do serwera, który z kolei nie ma możliwości rozróżniania połączonych z nim użytkowników. Wspomniane ograniczenie doprowadziło do opracowania protokołu WebSocket, który pozwala przeglądarkom internetowym na utrzymanie połączenia typu pełny dupleks z serwerem — obie strony mogą wówczas jednocześnie wysyłać i otrzymywać dane. API WebSocket otwiera zupełnie nowe możliwości przed aplikacjami sieciowymi opartymi na prowadzonej w czasie rzeczywistym komunikacji między klientem i serwerem. Problem z protokołem WebSocket polega na tym, że prace nad nim nie zostały jeszcze ukończone. Wprawdzie pewne przeglądarki internetowe oferują już obsługę WebSocket, ale nadal wiele starszych wersji, przede wszystkim Internet Explorer, nie obsługuje WebSocket. Biblioteka Socket.IO rozwiązuje ten problem przez wykorzystanie WebSocket w przeglądarkach internetowych obsługujących tę technologię oraz zastosowanie pewnych rozwiązań zapasowych przeznaczonych dla przeglądarek pozbawionych obsługi WebSocket. Dzięki temu zachowania naśladujące WebSocket są dostępne nawet w starszych przeglądarkach internetowych. W tym podrozdziale utworzymy dwie przykładowe aplikacje oparte na bibliotece Socket.IO: Minimalną aplikację Socket.IO, która czas serwera przekazuje połączonym z nim klientom. Aplikację Socekt.IO odświeżającą stronę po modyfikacji pliku CSS. Po utworzeniu wymienionych przykładowych aplikacji poznasz jeszcze kilka innych sposobów użycia biblioteki Socket.IO, co odbędzie się podczas modyfikacji utworzonej w rozdziale 4. aplikacji wyświetlającej postęp operacji przekazywania plików do serwera. Zaczynamy jednak od podstaw.

13.1.1. Tworzenie minimalnej aplikacji Socket.IO Przyjmujemy założenie, że chcemy utworzyć niewielką aplikację sieciową, która nieustannie będzie wyświetlała w przeglądarce internetowej aktualny czas UTC serwera. Tego rodzaju aplikacja będzie służyła do sprawdzania różnicy między czasem serwera i klientów. Zastanów się teraz, jak taką aplikację można

utworzyć za pomocą modułu http i dotąd poznanych frameworków. Wprawdzie istnieje możliwość opracowania pewnego rozwiązania opartego na sztuczkach takich jak użycie puli, ale biblioteka Socket.IO oferuje estetyczny interfejs do realizacji tego rodzaju zadań. Implementacja wspomnianej aplikacji z wykorzystaniem Socket.IO jest niezwykle prosta. Aby utworzyć aplikację, w pierwszej kolejności należy zainstalować Socket.IO za pomocą menedżera npm: npm install socket.io

W listingu 13.1 przedstawiono kod działający po stronie serwera. Umieść go w pliku o nazwie clock-server.js; kod będzie można wypróbować po przygotowaniu kodu działającego po stronie klienta. Listing 13.1. Serwer Socket.IO nieustannie przekazujący klientowi aktualny czas var app = require('http').createServer(handler); var io = require('socket.io').listen(app); Uaktualnienie zwykłego serwera HTTP do serwera Socket.IO. var fs = require('fs'); var html = fs.readFileSync('index.html', 'utf8'); function handler (req, res) { Kod serwera HTTP zawsze udostępnia plik index.html. res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Length', Buffer.byteLength(html, 'utf8')); res.end(html); } function tick () { var now = new Date().toUTCString(); Pobranie bieżącego czasu w formacie UTC. io.sockets.send(now); Wysłanie aktualnego czasu do wszystkich połączonych gniazd. } setInterval(tick, 1000); Wywołanie funkcji tick() co sekundę. app.listen(8080);

Jak możesz się przekonać, biblioteka Socket.IO minimalizuje ilość dodatkowego kodu, który trzeba umieścić w podstawowym serwerze HTTP. Tylko dwa wiersze kodu są potrzebne do użycia zmiennej io (to zmienna egzemplarza serwera Socket.IO) w celu przesyłania w czasie rzeczywistym komunikatów między serwerem i klientami. W omawianym przykładzie funkcja tick() jest wywoływana co sekundę i przekazuje aktualny czas serwera wszystkim połączonym z nim klientom. Kod serwera w pierwszej kolejności wczytuje do pamięci plik index.html, który teraz zaimplementujemy. Kod działający po stronie klienta przedstawiono w listingu 13.2.

Listing 13.2. Klient Socket.IO wyświetlający czas otrzymany z serwera



Aktualny czas w serwerze:

Wypróbowanie aplikacji Teraz można już wypróbować serwer. Uruchom go za pomocą wywołania node clock-server.js, a zobaczysz komunikat info - socket.io started. Oznacza to, że biblioteka Socket.IO została skonfigurowana i jest gotowa do otrzymywania połączeń. Uruchom więc przeglądarkę internetową i przejdź pod adres URL http://localhost:8080/. Powinieneś otrzymać wynik podobny do pokazanego na rysunku 13.1. Czas będzie uaktualniany co sekundę na podstawie komunikatu otrzymywanego z serwera. Uruchom śmiało inną przeglądarkę internetową, przejdź na ten sam adres URL i przekonaj się, że zmiana wartości czasu następuje jednocześnie w obu przeglądarkach.

Rysunek 13.1. Serwer czasu uruchomiony w oknie terminala i połączony z nim klient widoczny w przeglądarce internetowej

W ten sposób za pomocą biblioteki Socket.IO i zaledwie kilku wierszy kodu przygotowałeś prowadzoną w czasie rzeczywistym komunikację między serwerem i klientami. Inne rodzaje komunikatów obsługiwanych przez Socket.IO. Wysyłanie komunikatu do wszystkich połączonych gniazd to tylko jedno z rozwiązań udostępnianych przez Socket.IO i pozwalających na współpracę z połączonymi użytkownikami. Komunikaty można wysyłać także do poszczególnych gniazd, rozgłaszać je do wszystkich gniazd poza jednym wskazanym, a także można wysyłać zmienne (opcjonalne) komunikaty itd. Więcej informacji na ten temat znajdziesz w dokumentacji biblioteki Socket.IO dostępnej na stronie http://socket.io/#how-to-use. Skoro przekonałeś się, jak proste rozwiązania można stosować za pomocą biblioteki Socket.IO, teraz możemy przejść do innego przykładu, pokazującego użyteczność zdarzeń wysyłanych przez serwer.

13.1.2. Użycie biblioteki Socket.IO do odświeżenia strony i stylów CSS Oto jak w skrócie internetowych:

wygląda

typowy

sposób

pracy

projektanta

stron

1. Otworzenie strony internetowej w wielu przeglądarkach. 2. Wyszukanie elementów wymagających modyfikacji stylów. 3. Wprowadzenie odpowiednich zmian w jednym lub większej liczbie arkuszy stylów. 4. Ręczne odświeżenie strony we wszystkich przeglądarkach internetowych. 5. Powrót do kroku 2. Jedną z możliwości usprawnienia pracy jest automatyzacja kroku 4., w którym projektant musi ręcznie przejść do każdej przeglądarki internetowej i kliknąć przycisk odświeżający stronę. To zadanie jest szczególnie czasochłonne, gdy podczas pracy stronę trzeba przetestować w wielu przeglądarkach internetowych w różnych komputerach i urządzeniach mobilnych. Czy istnieje możliwość całkowitej eliminacji ręcznego odświeżania strony? Wyobraź sobie, że po zapisaniu arkusza stylów w edytorze tekstów wszystkie przeglądarki internetowe, w których jest ona wyświetlana, automatycznie odświeżają stronę i odzwierciedlają tym samym zmiany wprowadzone w CSS. To byłaby ogromna oszczędność czasu dla projektantów stron internetowych. Biblioteka Socket.IO w połączeniu z funkcjami Node fs.watchFile() i fs.watch()

umożliwia przygotowanie wspomnianego rozwiązania, na dodatek w zaledwie niewielu wierszach kodu. W

omawianym przykładzie użyjemy funkcji fs.watchFile() zamiast nowszej fs.watch(), ponieważ chcemy zachować gwarancję, że kod będzie działał dokładnie tak samo na wszystkich platformach. Dokładne omówienie sposobu działania funkcji fs.watch() znajdzie się w dalszej części rozdziału. fs.watchFile() kontra fs.watch(). Node.js oferuje API przeznaczone do obserwacji plików. Pierwsze z nich to funkcja fs.watchFile() (http://nodejs.org/api/fs.html#fs_fs_watchfile_filename_options_listener), która wykorzystuje sporą ilość zasobów, ale jest niezawodna i działa na wszystkich platformach. Drugie to funkcja fs.watch() (http://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener), która jest wysoce zoptymalizowana dla poszczególnych platform, ale jej zachowanie jest odmienne na pewnych platformach. Dokładne omówienie tej funkcji znajdziesz w punkcie 13.3.2. W omawianym przykładzie połączymy użycie frameworka Express i biblioteki Socket.IO. Oba wymienione komponenty doskonale ze sobą współpracują, podobnie jak użyty w poprzednim przykładzie http.Server. Najpierw zapoznamy się z kodem działającym po stronie serwera. Kod przedstawiony w listingu 13.3 umieść w pliku o nazwie watch-server.js, jeśli będziesz chciał ostatecznie uruchomić tworzoną tutaj aplikację. Listing 13.3. Serwer utworzony za pomocą Express/Socket.IO powoduje wywołanie zdarzenia po wykryciu zmiany w pliku var fs = require('fs'); var url = require('url'); var http = require('http'); var path = require('path'); var express = require('express'); var app = express(); Utworzenie aplikacji serwera Express. var server = http.createServer(app); var io = require('socket.io').listen(server); Opakowanie serwera HTTP w celu utworzenia egzemplarza serwera Socket.IO. var root = __dirname; Użycie metody pośredniczącej do monitorowania plików wskazanych przez metodę pośredniczącą static(). app.use(function (req, res, next) { Zarejestrowanie zdarzenia statycznego emitowanego przez metodę pośredniczącą static().

var file = url.parse(req.url).pathname;

var mode = 'stylesheet'; if (file[file.length - 1] == '/') { file += 'index.html'; mode = 'reload'; } createWatcher(file, mode); Określenie nazwy pliku i wywołanie createWatcher(). next(); }); app.use(express.static(root)); Konfiguracja serwera jako prostego serwera pliku statycznego. var watchers = {}; Zachowanie listy aktywnych plików, które są monitorowane. function createWatcher (file, event) { var absolute = path.join(root, file); if (watchers[absolute]) { return; } fs.watchFile(absolute, function (curr, prev) { Rozpoczęcie monitorowania pliku pod kątem zmian. if (curr.mtime !== prev.mtime) { Sprawdzenie, czy zmianie uległ mtime (czas ostatniej modyfikacji). Jeżeli tak, należy wywołać zdarzenie Socket.IO. io.sockets.emit(event, file); } }); watchers[absolute] = true; Oznaczenie pliku jako monitorowanego. } server.listen(8080);

Na tym etapie masz w pełni funkcjonalny serwer plików statycznych przygotowany do wywoływania u klientów zdarzeń reload i stylesheet za pomocą biblioteki Socket.IO. Teraz zajmiemy się kodem działającym po stronie klienta. Kod przedstawiony w listingu 13.4 umieść w pliku index.html, aby został udostępniony po uruchomieniu serwera i podaniu ścieżki dostępu do katalogu głównego. Listing 13.4. Kod działający po stronie klienta odpowiedzialny za ponowne wczytanie stylów po otrzymaniu zdarzenia z serwera

Dynamiczne ponowne wczytywanie arkuszy stylów CSS za pomocą Socket.IO





To jest nasza wspaniała strona internetowa!

Jeżeli ten plik (index.html) zostanie zmodyfikowany, wówczas za pomocą biblioteki Socket.IO serwer wyśle komunikat do przeglądarki internetowej nakazujący odświeżenie strony.

Jeżeli którykolwiek arkusz stylów (header.css lub styles.css) zostanie zmodyfikowany, wówczas za pomocą biblioteki Socket.IO serwer wyśle komunikat do przeglądarki internetowej nakazujący dynamiczne wczytanie stylów CSS bez odświeżenia strony.





Wypróbowanie aplikacji Zanim aplikacja będzie działała, konieczne jest utworzenie kilku plików CSS (header.css i styles.css), ponieważ plik index.html wczytuje wymienione arkusze

stylów podczas generowania jego zawartości. Po przygotowaniu kodu działającego po stronie serwera, pliku index.html i arkuszy stylów używanych przez przeglądarkę internetową można wypróbować aplikację. Uruchom więc serwer: $ node watch-server.js

Po uruchomieniu serwera otwórz przeglądarkę internetową i przejdź na stronę http://localhost:8080, a zobaczysz udostępnioną wygenerowaną prostą stronę HTML. Teraz spróbuj zmodyfikować jeden z plików CSS (na przykład zmień kolor tła dla znacznika ). Przekonasz się, że arkusz stylów zostanie ponownie wczytany w przeglądarce internetowej bez odświeżenia samej strony. Spróbuj wyświetlić podaną stronę jednocześnie w wielu przeglądarkach internetowych. W omawianym przykładzie reload i stylesheet to własne zdefiniowane zdarzenia aplikacji — one nie są częścią API biblioteki Socket.IO. Na podstawie omówionego przykładu przekonałeś się, że obiekt socket działa w charakterze dwukierunkowego EventEmitter, którego można użyć do emisji zdarzeń transferowanych później przez Socket.IO do klienta.

13.1.3. Inne zastosowania dla biblioteki Socket.IO Jak zapewne wiesz, protokół HTTP nie został opracowany w celu zapewnienia jakiegokolwiek rodzaju komunikacji w czasie rzeczywistym. Jednak dzięki zastosowaniu technologii takich jak WebSocket i modułów takich jak biblioteka Socket.IO wspomniane ograniczenie zostało pokonane. W ten sposób otworzono drogę pozwalającą na opracowywanie wielu nowych rodzajów aplikacji sieciowych działających w przeglądarkach internetowych, co wcześniej było niemożliwe. W rozdziale 4. dowiedziałeś się, że biblioteka Socket.IO będzie doskonałym rozwiązaniem do przekazywania przeglądarce internetowej zdarzeń informujących o postępie podczas transferu pliku do serwera. Istnieje możliwość użycia także własnego zdarzenia progress: form.on('progress', function(bytesReceived, bytesExpected) { Uaktualniona wersja przykładu z punktu 4.4.3. var percent = Math.floor(bytesReceived / bytesExpected * 100); socket.emit('progress', { percent: percent }); Przekazanie za pomocą Socket.IO informacji wyrażonych w procentach. });

Aby przedstawione rozwiązanie działało, konieczne jest uzyskanie dostępu do e gz e mpla r z a socket dopasowującego przeglądarkę internetową, która przekazuje plik do serwera. To wykracza poza zakres tematyczny niniejszej

książki, ale zasoby dostępne w internecie mogą okazać się tutaj pomocne. (Początkujący powinni zapoznać się z artykułem Daniela Bauliga zatytułowanym „socket.io and Express: tying it all together”, który opublikowano na blogu blinzeln pod adresem http://www.danielbaulig.de/socket-ioexpress/). Biblioteka Socket.IO zmieniła zasady gry. Jak wcześniej wspomniano, programiści zainteresowani tworzeniem działających w czasie rzeczywistym aplikacji sieciowych bardzo często słyszeli o tej bibliotece, jeszcze zanim dowiedzieli się o istnieniu Node.js — to potwierdzenie, jak wpływowa i ważna jest biblioteka Socket.IO. Informacje dotyczące biblioteki nieustannie pojawiają się w społecznościach zajmujących się grami sieciowymi, jest ona używana do tworzenia bardziej kreatywnych gier i aplikacji, niż można to sobie wyobrazić. Wspomniana biblioteka to również bardzo popularny wybór w aplikacjach tworzonych w technologiach uznawanych za konkurencyjne dla Node, na przykład Node Knockout (http://nodeknockout.com/). Do jakich zapierających dech w piersiach celów ją wykorzystasz?

13.2. Dokładniejsze omówienie sieci TCP/IP Node to technologia doskonale przystosowana dla aplikacji działających w sieci, ponieważ na ogół używanie ich oznacza dużą ilość operacji wejścia-wyjścia. Poza serwerami HTTP, o których sporo się dowiedziałeś z tej książki, Node obsługuje praktycznie każdy rodzaj sieci opartej na TCP. Zatem platforma Node jest odpowiednia do utworzenia na przykład serwera poczty elektronicznej, plików lub proxy, a ponadto może być używana przez klientów dla wymienionego rodzaju usług. Technologia dostarcza kilku narzędzi pomagających w tworzeniu aplikacji o wysokiej jakości i zapewniających dużą wydajność w zakresie operacji wejścia-wyjścia. Więcej informacji na ten temat znajdziesz w tym podrozdziale. Pewne protokoły sieciowe wymagają wartości odczytywanych na poziomie bajtów — znaków, liczb całkowitych, liczb zmiennoprzecinkowych i innych typów danych obejmujących dane binarne. Jednak JavaScript nie oferuje żadnych rodzimych binarnych typów danych, z którymi można pracować. Najbliższe rozwiązanie to zastosowanie szalonych sztuczek wobec ciągów tekstowych. W Node zastosowano luźną implementację własnego typu danych Buffer, który działa jako fragment danych binarnych o stałej wielkości. To pozwala na uzyskanie dostępu na poziomie bajtów, co jest wymagane do implementacji innych protokołów. W tym podrozdziale zostaną poruszone wymienione poniżej zagadnienia: praca z buforami i danymi binarnymi, utworzenie serwera TCP,

utworzenie klienta TCP. Na początek przekonajmy się, jak Node współpracuje z danymi binarnymi.

13.2.1. Praca z buforami i danymi binarnymi to specjalny typ danych, który Node dostarcza programistom. Działa na zasadzie pojemnika dla niezmodyfikowanych danych binarnych o stałej wielkości. Bufor można potraktować jako odpowiednik funkcji języka C o nazwie malloc() lub słowa kluczowego new w C++. Bufory to bardzo szybkie i lekkie obiekty, są stosowane w podstawowym API Node. Na przykład domyślnie znajdują się w wartości zwrotnej zdarzeń data wszystkich klas Stream. Buffer

Node globalnie udostępnia konstruktor Buffer, zachęcając tym samym programistę do jego użycia jako rozszerzenia zwykłych typów JavaScript. Z programistycznego punktu widzenia bufory można traktować podobnie jak tablice. Różnice polegają na braku możliwości zmiany wielkości buforów, a ponadto bufory mogą przechowywać jedynie wartości w postaci liczb całkowitych z zakresu od 0 do 255. W ten sposób są idealnym rozwiązaniem do przechowywania danych binarnych praktycznie wszystkiego. Ponieważ bufory działają z niezmodyfikowanymi bajtami, można je wykorzystać do implementacji na niskim poziomie dowolnego protokołu.

Dane tekstowe kontra binarne Przyjmujemy założenie, że w pamięci chcesz przechowywać liczbę 121 234 869, używając do tego typu Buffer. Domyślnie Node przyjmuje założenie, że programista chce pracować z danymi tekstowymi w buforach. Dlatego też przekazanie ciągu tekstowego "121234869" konstruktorowi Buffer spowoduje alokację nowego obiektu Buffer wraz z zapisaną wartością w postaci ciągu tekstowego: var b = new Buffer("121234869"); console.log(b.length); 9 console.log(b);

W omawianym przykładzie wartością zwrotną jest dziewięciobajtowy obiekt Buffer. Wynika to z zapisu ciągu tekstowego w obiekcie Buffer za pomocą domyślnego, czytelnego dla człowieka kodowania tekstowego (UTF-8), w którym ciąg tekstowy zawiera po jednym bajcie dla każdego znaku. Node zawiera także funkcje pomocnicze przeznaczone do odczytu i zapisu danych binarnych (czytelnych dla komputera) w postaci liczb całkowitych. Są

one niezbędne do implementacji protokołów maszynowych, które wysyłają niezmodyfikowane typy danych (na przykład liczby całkowite, zmiennoprzecinkowe, o podwójnej precyzji itd.). Ponieważ w omawianym przykładzie chcemy przechowywać wartość liczbową, znacznie efektywniejszym rozwiązaniem będzie wykorzystanie funkcji pomocniczej o nazwie writeInt32LE() do zapisu liczby 121 234 869 jako czytelnych dla komputera danych binarnych (przyjęto założenie o użyciu kolejności bajtów little endian) w czterobajtowym obiekcie Buffer. Istnieją jeszcze inne odmiany funkcji pomocniczych Buffer: writeInt16LE() dla mniejszych liczb całkowitych; writeUInt32LE() dla wartości bez znaku; writeInt32BE() dla wartości stosującej kolejność bajtów big endian. Dostępnych funkcji jest znacznie więcej, a jeśli chcesz poznać wszystkie, to więcej informacji znajdziesz na stronie dokumentacji API obiektu Buffer (http://nodejs.org/docs/latest/api/buffer.html). W poniższym fragmencie kodu liczba została zapisana za pomocą binarnej funkcji pomocniczej writeInt32LE(): var b = new Buffer(4); b.writeInt32LE(121234869, 0); console.log(b.length); 4 console.log(b);

Przechowywanie w pamięci wartości w postaci binarnej liczby całkowitej zamiast ciągu tekstowego oznacza zmniejszenie wielkości danych o połowę — z 9 bajtów do 4. Na rysunku 13.2 pokazano schemat obu buforów i różnice między protokołem czytelnym dla człowieka (tekst) i czytelnym dla komputera (dane binarne).

Rysunek 13.2. Różnica na poziomie bajtów między liczbą 121 234 869 przedstawioną w postaci ciągu tekstowego a binarną liczbą całkowitą przedstawioną z zastosowaniem kolejności bajtów little endian

Niezależnie od używanego protokołu oferowana przez Node klasa w stanie obsłużyć poprawnie sposób przedstawienia danych.

Buffer

będzie

Kolejność bajtów. Pojęcie kolejności bajtów odnosi się do ich kolejności w sekwencji wielobajtowej. Kiedy bajty są w kolejności little endian, najmniej znaczący bajt (ang. Least Significant Byte, LSB) jest przechowywany jako pierwszy, a sekwencja bajtów jest odczytywana od prawej do lewej strony. Natomiast kolejność big endian oznacza, że jako pierwszy jest przechowywany najbardziej znaczący bajt (ang. Most Significant Byte, MSB), a sekwencja bajtów jest odczytywana od lewej do prawej strony. Node.js oferuje funkcje pomocnicze dla typów danych obsługujących obie kolejności, czyli little endian i big endian. Teraz możemy już wykorzystać wspomniane obiekty utworzenie serwera TCP i rozpoczęcie z nim pracy.

Buffer

w praktyce przez

13.2.2. Tworzenie serwera TCP Podstawowe API Node działa na niskim poziomie i udostępnia jedynie niezbędną infrastrukturę dla modułów zbudowanych na jego podstawie. Doskonałym przykładem jest tutaj moduł http, utworzony na podstawie modułu net i przeznaczony do implementacji protokołu HTTP. Inne protokoły, na przykład SMTP dla poczty elektronicznej lub FTP dla transferu plików, muszą być zaimplementowane również na bazie modułu net, ponieważ podstawowe API

Node nie implementuje żadnych protokołów działających na wysokim poziomie.

Zapis danych Moduł net oferuje interfejs niezmodyfikowanego gniazda TCP/IP do użycia w aplikacji. API przeznaczone do tworzenia serwera TCP jest bardzo podobne do używanego w trakcie tworzenia serwera HTTP: wywołujesz net.createServer() i podajesz nazwę funkcji wywołania zwrotnego, która będzie wykonana po każdym połączeniu. Podstawowa różnica związana z tworzeniem serwera TCP polega na tym, że funkcja wywołania zwrotnego pobiera tylko jeden argument (zwykle o nazwie socket) będący obiektem Socket. Natomiast podczas tworzenia serwera HTTP używane są argumenty req i res. Klasa Socket. Wymieniona klasa jest używana zarówno przez klienty, jak i serwer podczas stosowania modułu net w Node. To jest podklasa klasy Stream umożliwiająca odczyt (readable) i zapis (writeable). Oznacza to, że emituje zdarzenia data podczas odczytu danych wejściowych z gniazda, a także ma funkcje write() i end() przeznaczone do wysyłania danych wyjściowych. Spójrz teraz na obiekt net.Server oczekujący na połączenia, a następnie wykonujący funkcję wywołania zwrotnego. W omawianym przypadku logika zdefiniowana w funkcji wywołania zwrotnego powoduje po prostu przekazanie do gniazda komunikatu Witaj, świecie! i eleganckie zamknięcie połączenia: var net = require('net'); net.createServer(function (socket) { socket.write('Witaj, świecie!\r\n'); socket.end(); }).listen(1337); console.log('Serwer nasłuchiwanie na porcie 1337');

Uruchom serwer w celu przeprowadzenia pewnych testów: $ node server.js Serwer nasłuchiwanie na porcie 1337

Jeżeli za pomocą przeglądarki internetowej spróbujesz nawiązać połączenie z serwerem, próba zakończy się niepowodzeniem, ponieważ serwer nie potrafi się komunikować, używając protokołu HTTP, a jedynie przez niezmodyfikowany TCP. W celu nawiązania połączenia z serwerem i wyświetlenia komunikatu konieczne jest użycie odpowiedniego klienta TCP, na przykład w postaci polecenia netcat: $ netcat localhost 1337 Witaj, świecie

Doskonale! Teraz możemy wypróbować polecenie

telnet:

$ telnet localhost 1337 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Witaj, świecie! Connection closed by foreign host.

Polecenie telnet jest najczęściej używane w trybie interaktywnym, a więc oprócz komunikatu Witaj, świecie! wyświetla jeszcze inne, wygenerowane przez polecenie. Zgodnie z oczekiwaniami komunikat serwera jest wyświetlany tuż przed zamknięciem połączenia. Jak możesz się przekonać, zapis danych do gniazda jest całkiem łatwym zadaniem. Używasz wywołania write() oraz na końcu end(). Podczas przygotowywania odpowiedzi dla klienta omówione API celowo odpowiada API obiektu HTTP res.

Odczyt danych Serwery bardzo często stosują paradygmat żądanie-odpowiedź, w którym klient nawiązuje połączenie i natychmiast wysyła pewne żądanie. Serwer odczytuje żądanie, przetwarza odpowiedź, a następnie przekazuje ją do gniazda. Dokładnie w taki sposób działa protokół HTTP, a także większość innych protokołów sieciowych. Dlatego też bardzo ważne jest zrozumienie, jak dane są odczytywane. Jeżeli pamiętasz, jak można odczytać dane żądania z obiektu HTTP req, to odczyt danych z gniazda TCP nie powinien sprawić Ci żadnego problemu. Przy zachowaniu zgodności z możliwym do odczytu interfejsem Stream Twoje zadanie sprowadza się do nasłuchiwania zdarzeń data zawierających dane wejściowe, które zostały odczytane z gniazda: socket.on('data', function (data) { console.log('got "data"', data); });

Domyślnie nie jest stosowane żadne kodowanie znaków wobec socket, a więc argument data będzie egzemplarzem Buffer. Zwykle takie rozwiązanie jest oczekiwane (i dlatego zostało zdefiniowane jako domyślne). Jednak czasami znacznie wygodniejsze może się okazać wywołanie funkcji setEncoding() i nakazanie zdekodowania argumentu data na postać ciągu tekstowego, a nie bufora. Istnieje również możliwość nasłuchiwania zdarzenia end i tym samym ustalenia, że klient zamknął połączenie z gniazdem i nie należy już wysyłać kolejnych danych: socket.on('end', function () {

console.log('Gniazdo zostało zamknięte'); });

Bardzo łatwo można utworzyć klienta TCP, który będzie wyszukiwał ciąg tekstowy wersji danego serwera SSH przez po prostu oczekiwanie na pierwsze zdarzenie data: var net = require('net'); var socket = net.connect({ host: process.argv[2], port: 22 }); socket.setEncoding('utf8'); socket.once('data', function (chunk) { console.log('Wersja serwera SSH: %j', chunk.trim()); socket.end(); });

Teraz wypróbuj aplikację. Warto pamiętać o jednym: w tym uproszczonym przykładzie przyjęto założenie, że cały ciąg tekstowy zawierający informacje o wersji serwera zostanie dostarczony w jednym fragmencie. W większości przypadków takie rozwiązanie sprawdza się doskonale, ale dobry program powinien buforować dane wejściowe aż do napotkania znaku \n. Sprawdźmy, jakiej wersji serwera SSH używa serwis GitHub: $ node client.js github.com Wersja serwera SSH: "SSH-2.0-OpenSSH_5.5p1 Debian-6+squeeze1+github8"

Połączenie dwóch strumieni za pomocą socket.pipe()

Użycie funkcji pipe() (http://nodejs.org/api/stream.html#stream_readable_stream_pipe_destination_o w połączeniu z odczytywalnymi lub zapisywalnymi fragmentami obiektu Socket to również dobry pomysł. Jeżeli chcesz utworzyć prosty serwer TCP wyświetlający klientowi wszystko to, co zostało przekazane do serwera, wystarczy tylko pojedynczy wiersz kodu w funkcji wywołania zwrotnego: socket.pipe(socket);

Powyższy przykład pokazuje, że wystarczy tylko jeden wiersz kodu do zaimplementowania protokołu IETF Echo (http://tools.ietf.org/rfc/rfc862.txt). Co ważniejsze jednak, przykład pokazuje możliwość użycia funkcji pipe() w celu przekazywania danych do oraz z obiektu socket. Oczywiście z reguły stosowane będą znacznie pożyteczniejsze egzemplarze strumieni, na przykład systemu plików lub gzip.

Obsługa nieeleganckiego zamknięcia połączenia Ostatnią rzeczą, którą trzeba uwzględnić w serwerze TCP, jest konieczność przewidywania klientów zamykających połączenie, ale nie zamykających prawidłowo gniazda. W przypadku polecenia netcat tego rodzaju sytuacja

wystąpi po naciśnięciu klawiszy Ctrl+C w celu zamknięcia procesu zamiast naciśnięcia Ctrl+D w celu eleganckiego zamknięcia połączenia. Aby wykryć wspomnianą sytuację, należy nasłuchiwać zdarzeń close: socket.on('close', function () { console.log('Klient został rozłączony'); });

Jeżeli po zamknięciu połączenia trzeba będzie zamknąć jeszcze gniazdo, wtedy odpowiednie operacje należy przeprowadzać z poziomu zdarzenia close, a nie end, ponieważ drugie z wymienionych nie zostanie wywołane w przypadku nieeleganckiego zamknięcia połączenia.

Zebranie wszystkiego w całość Teraz zbierzemy wszystkie zdarzenia i utworzymy prosty serwer typu echo, który wyświetla w terminalu komunikaty po wystąpieniu różnych zdarzeń. Kod serwera został przedstawiony w listingu 13.5. Listing 13.5. Prosty serwer TCP, który klientowi wyświetla z powrotem wszystkie otrzymane od niego dane var net = require('net'); net.createServer(function (socket) { console.log('Nawiązano połączenie z gniazdem!'); socket.on('data', function (data) { Zdarzenie data może wystąpić wielokrotnie. console.log('Zdarzenie "data"', data); }); socket.on('end', function () { console.log('Zdarzenie "end"'); Zdarzenie end może wystąpić tylko raz dla gniazda. }); socket.on('close', function () { Zdarzenie close również może wystąpić tylko raz dla gniazda. console.log('Zdarzenie "close"'); }); socket.on('error', function (e) { Zdefiniowanie obsługi błędów, aby uniknąć niezgłoszonych wyjątków. console.log('Zdarzenie "error"', e); }); socket.pipe(socket); }).listen(1337);

Uruchom serwer i nawiąż z nim połączenie za pomocą polecenia netcat lub telnet, a następnie wypróbuj serwer. Kiedy w aplikacji klienta zaczniesz

wpisywać znaki z klawiatury, powinieneś widzieć wywołania console.log() dotyczące zdarzeń, o których komunikaty są przekazywane do standardowego wyjścia serwera. Skoro potrafisz już tworzyć działające na niskim poziomie serwery TCP w Node, to prawdopodobnie zastanawiasz się, jak w Node przygotować program klienta przeznaczony do interakcji ze wspomnianymi serwerami. Teraz zajmiemy się więc programem klienta.

13.2.3. Tworzenie klienta TCP Node to nie tylko oprogramowanie przeznaczone do tworzenia serwerów. Utworzenie programu klienta sieciowego w Node również jest łatwe i użyteczne. Podczas tworzenia niezmodyfikowanych połączeń z serwerem TCP kluczowe znaczenie ma funkcja net.connect(). Wymieniona funkcja akceptuje wartości host i port, a zwraca egzemplarz socket. Obiekt socket zwrócony przez net.connect() jest odłączony od serwera, a więc najczęściej należy nasłuchiwać zdarzeń connect przed rozpoczęciem wykonywania jakiejkolwiek operacji z gniazdem: var net = require('net'); var socket = net.connect({ port: 1337, host: 'localhost' }); socket.on('connect', function () { // Rozpoczęcie tworzenia "żądania". socket.write('HELO local.domain.name\r\n'); ... });

Kiedy egzemplarz socket nawiąże połączenie z serwerem, wtedy zaczyna zachowywać się jak egzemplarze socket, z którymi masz do czynienia w funkcji wywołania zwrotnego net.Server. Przystępujemy teraz do przygotowania prostej replikacji polecenia netcat. Kod tworzonego klienta przedstawiono w listingu 13.6. Ogólnie rzecz biorąc, program nawiązuje połączenie ze wskazanym zdalnym serwerem i potokuje standardowe wejście z programu do gniazda, a następnie potokuje odpowiedź gniazda do standardowego wyjścia programu. Listing 13.6. Podstawowa replika polecenia netcat przygotowana za pomocą Node var net = require('net'); var host = process.argv[2]; var port = Number(process.argv[3]);ó Przetworzenie argumentów host i port podanych w powłoce. var socket = net.connect(port, host); Utworzenie egzemplarza socket i rozpoczęcie

procedury nawiązywania połączenia z serwerem. socket.on('connect', function () { Obsługa zdarzenia connect po nawiązaniu połączenia z serwerem. process.stdin.pipe(socket); Potokowanie standardowego wejścia procesu do gniazda. socket.pipe(process.stdout); Potokowanie danych gniazda do standardowego wyjścia procesu. process.stdin.resume(); Wywołanie resume() dla danych wejściowych, aby rozpocząć odczyt danych. }); socket.on('end', function () { Wstrzymanie danych ze standardowego wejścia po wystąpieniu zdarzenia end. process.stdin.pause(); });

Przygotowanego klienta można wykorzystać do nawiązywania połączeń z utworzonymi wcześniej serwerami TCP. Jeżeli jesteś fanem „Gwiezdnych wojen”, spróbuj uruchomić przedstawioną replikację polecenia netcat wraz z poniższym argumentem, aby otrzymać efekt specjalny: $ node netcat.js towel.blinkenlights.nl 23

Usiądź wygodnie i ciesz się danymi wyjściowymi, które pokazano na rysunku 13.3. Zasłużyłeś na przerwę.

Rysunek 13.3. Nawiązanie połączenia z serwerem ASCII Star Wars za pomocą skryptu netcat.js

To już wszystko w zakresie tworzenia za pomocą Node działających na niskim

poziomie serwerów TCP i klientów. Moduł net oferuje proste, choć obszerne API, natomiast klasa Socket stosuje zgodnie z oczekiwaniami odczytywalny i zapisywalny interfejs Stream. W zasadzie moduł net to próbka podstawowych możliwości oferowanych przez Node. Zmienimy narzędzia raz jeszcze i przyjrzymy się, w jaki sposób podstawowe API Node pozwala na współpracę ze środowiskiem procesu oraz na zbieranie informacji dotyczących środowiska uruchomieniowego i systemu operacyjnego.

13.3. Narzędzia przeznaczone do pracy z systemem operacyjnym Bardzo często zachodzi potrzeba współpracy ze środowiskiem, w którym działa Node. Może się to wiązać na przykład z koniecznością sprawdzenia zmiennych środowiskowych w celu włączenia rejestracji danych podczas procesu debugowania. Kolejny przykład to implementacja sterownika dżojstika w systemie Linux przy użyciu działających na niskim poziomie funkcji modułu fs przeznaczonych do pracy z /dev/js0 (plik urządzenia dla dżojstika). Jeszcze inny przykład to konieczność uruchomienia procesu potomnego, takiego jak php, do kompilacji starszego skryptu PHP. Wszystkie wymienione rodzaje operacji wymagają użycia pewnego podstawowego API Node. Oto komponenty, które zostaną omówione w tym podrozdziale: Obiekt globalny o nazwie process. Zawiera informacje o bieżącym procesie, takie jak użyte argumenty oraz aktualnie ustawione zmienne środowiskowe. Moduł fs. Zawiera działające na wysokim poziomie klasy ReadStream i WriteStream, które powinieneś już znać. Ponadto obejmuje działające na niskim poziomie funkcje, które zostaną omówione w tym podrozdziale. Moduł child_process. Zawiera działające zarówno na wysokim, jak i niskim poziomie interfejsy przeznaczone do tworzenia procesów potomnych oraz obsługi specjalnego sposobu tworzenia egzemplarzy node wraz z dwukierunkowym kanałem przekazywania informacji. Obiekt process jest jednym z API najczęściej wykorzystywanych przez programy i dlatego zapoznamy się z nim na początku.

13.3.1. Obiekt process, czyli globalny wzorzec

Singleton Każdy proces Node posiada globalny obiekt process współdzielony przez wszystkie moduły. Wymieniony obiekt zawiera użyteczne informacje o procesie i kontekście, w którym działa. Na przykład argumenty podane Node podczas uruchamiania aktualnego skryptu są dostępne jako process.argv, natomiast zmienne środowiskowe można pobrać i ustawić za pomocą process.env. Jednak najbardziej interesującą cechą obiektu process jest to, że stanowi on egzemplarz EventEmitter i emituje zdarzenia specjalne, takie jak exit i uncaughtException. Obiekt process ma wiele możliwości, a pewne API nieomówione w tym punkcie zostanie przedstawione w dalszej części rozdziału. W tym punkcie koncentrujemy się na następujących zagadnieniach: Użycie process.env do pobierania i ustawiania zmiennych środowiskowych. Nasłuchiwanie zdarzeń specjalnych emitowanych przez obiekt process, na przykład exit i uncaughtException. Nasłuchiwanie emitowanych przez obiekt process zdarzeń sygnałów, na przykład SIGUSR2 i SIGKILL.

Użycie process.env do pobierania i ustawiania zmiennych środowiskowych Zmienne środowiskowe to doskonałe rozwiązanie pozwalające na zmianę sposobu działania programu lub modułu. Tego rodzaju zmienne można wykorzystać na przykład do skonfigurowania serwera i wskazania portu, na którym ma on nasłuchiwać. Z kolei system operacyjny może ustawić zmienną TMPDIR w celu określenia lokalizacji przeznaczonej na generowane przez program pliki tymczasowe, które później mogą być usunięte. Zmienne środowiskowe. Być może nie znasz jeszcze zastosowania zmiennych środowiskowych, powinieneś jednak wiedzieć, że to pary typu klucz--wartość, które mogą być używane przez dowolny proces do zmiany sposobu jego zachowania. Wszystkie systemy operacyjne używają zmiennej środowiskowej PATH do zdefiniowania listy ścieżek dostępu sprawdzanych podczas wyszukiwania programu po jego nazwie (na przykład pełna ścieżka dostępu polecenia ls to /bin/ls). Przyjmujemy założenie, że chcesz włączyć rejestrację danych w trybie debugowania podczas tworzenia modułu lub usuwania z niego błędów. Wspomniana rejestracja ma być niedostępna w trakcie zwykłego użycia modułu, ponieważ może irytować jego użytkowników. Doskonałym rozwiązaniem będzie użycie zmiennych środowiskowych. Można ustalić, czy została ustawiona

z mi e nna DEBUG. Odbywa się przedstawiono w listingu 13.7.

to

przez

sprawdzenie

process.env.DEBUG,

jak

Listing 13.7. Zdefiniowanie funkcji debug() na podstawie zmiennej środowiskowej DEBUG var debug; if (process.env.DEBUG) { Zdefiniowanie działania funkcji na podstawie wartości process.env.DEBUG. debug = function (data) { console.error(data); Gdy zmienna DEBUG jest ustawiona, funkcja debug() będzie przekazywać argument do standardowego wyjścia błędów. }; } else { debug = function () {}; Gdy zmienna DEBUG nie jest ustawiona, funkcja debug() będzie pusta i nie wykona żadnej operacji. } debug('To jest wywołanie debugujące'); Wywołanie funkcji debug() w różnych miejscach kodu. console.log('Witaj, świecie!'); debug('To jest inne wywołanie debugujące');

Jeżeli przedstawiony skrypt zostanie uruchomiony w zwykły sposób (bez ustawienia zmiennej środowiskowej process.env.DEBUG), wówczas wywołanie debug() nie da żadnego efektu, ponieważ wywoływana jest pusta funkcja: $ node debug-mode.js Witaj, świecie!

Aby przetestować tryb debugowania, konieczne jest ustawienie zmiennej środowiskowej process.env.DEBUG. Najłatwiejszym sposobem jest dołączenie DEBUG=1 do polecenia uruchamiającego egzemplarz Node. W trybie debugowania wywołanie funkcji debug() spowoduje nie tylko wygenerowanie standardowych danych wyjściowych, ale również umieszczenie odpowiedniego komunikatu w konsoli. To bardzo użyteczne rozwiązanie pozwalające na zbieranie danych diagnostycznych podczas rozwiązywania problemów związanych z kodem: $ DEBUG=1 node debug-mode.js To jest wywołanie debugujące Witaj, świecie! To jest inne wywołanie debugujące

Opracowany przez T.J. Holowaychuka moduł debug (https://github.com/visionmedia/debug) hermetyzuje dokładnie tę samą funkcjonalność, a ponadto zawiera wiele funkcji dodatkowych. Jeżeli lubisz przedstawioną tutaj technikę debugowania, zdecydowanie powinieneś zapoznać się z modułem debug.

Zdarzenia specjalne emitowane przez obiekt proces Istnieją dwa zdarzenia specjalne emitowane przez obiekt process: exit. To zdarzenie jest emitowane tuż przed zakończeniem działania przez proces. uncaughtException. To zdarzenie jest emitowane za każdym razem, gdy wystąpi nieobsłużony błąd. Zdarzenie exit ma istotne znaczenie dla każdej aplikacji, która musi wykonać jakiekolwiek operacje przed zakończeniem działania programu, na przykład usunąć obiekt lub umieścić w konsoli ostateczny komunikat. Warto pamiętać, że zdarzenie exit jest emitowane już po zatrzymaniu pętli zdarzeń, a więc nie masz możliwości uruchomienia jakiegokolwiek zadania asynchronicznego w trakcie zdarzenia exit. Kod wyjścia jest przekazywany jako pierwszy argument, kod 0 oznacza sukces. Utworzymy teraz skrypt, który nasłuchuje zdarzenia komunikat Koniec pracy...:

exit,

a następnie generuje

process.on('exit', function (code) { console.log('Koniec pracy...'); });

Inne zdarzenie specjalne emitowane przez obiekt process to uncaughtException. W perfekcyjnym programie nigdy nie wystąpią nieobsłużone wyjątki, ale w rzeczywistości lepiej być przygotowanym na taką ewentualność, niż później żałować. Jedynym argumentem przekazywanym do zdarzenia uncaughtException jest nieprzechwycony obiekt Error. Kiedy nie ma innych obiektów nasłuchujących zdarzeń „błędu”, wszelkie nieprzechwycone błędy doprowadzą do awarii procesu (to jest zachowanie domyślne w większości aplikacji). Jednak istnienie co najmniej jednego obiektu nasłuchującego daje możliwość podjęcia dowolnego działania po przechwyceniu błędu. Node nie zakończy automatycznie działania, choć takie rozwiązanie jest konieczne we własnych wywołaniach zwrotnych. Dokumentacja Node.js wyraźnie ostrzega, że każde użycie zdarzenia uncaughtException powinno zawierać wywołanie process.exit() w wywołaniu zwrotnym, ponieważ w przeciwnym razie aplikacja pozostanie w niezdefiniowanym stanie, co jest złym rozwiązaniem. Przygotujemy teraz obiekt nasłuchujący zdarzeń uncaughtException, a następnie zgłosimy nieprzechwycony błąd, aby zobaczyć, jak to wygląda w praktyce: process.on('uncaughtException', function (err) { console.error('Wystąpił nieprzechwycony wyjątek:', err.message);

process.exit(1); }); throw new Error('Nieprzechwycony wyjątek');

Po wystąpieniu nieoczekiwanego błędu kod będzie w stanie go przechwycić i przeprowadzić odpowiednie operacje przed zakończeniem działania procesu.

Przechwytywanie sygnałów wysyłanych procesowi W systemie UNIX wprowadzono koncepcję sygnałów, które stanowią podstawową formę komunikacji międzyprocesowej (ang. Interprocess Communication, IPC). Wspomniane sygnały są bardzo proste i pozwalają na użycie jedynie pewnego stałego zestawu nazw, a ponadto są przekazywane bez argumentów. Node posiada zdefiniowane domyślne zachowanie dla kilku wymienionych poniżej sygnałów: SIGINT. Sygnał wysyłany przez powłokę po naciśnięciu klawiszy Ctrl+C. Domyślne zachowanie Node powoduje zakończenie działania procesu. To zachowanie można zmienić za pomocą pojedynczego nasłuchującego sygnału SIGINT w obiekcie process. SIGUSR1. Po otrzymaniu tego sygnału Node „wejdzie” do wbudowanego debugera. SIGWINCH. Ten sygnał jest wysyłany przez powłokę po zmianie wielkości okna terminalu. Node zeruje wartości process.stdout.rows i process.stdout.columns i emituje zdarzenie resize po otrzymaniu omawianego sygnału. Są to trzy sygnały domyślne obsługiwane przez Node. Istnieje możliwość nasłuchiwania w obiekcie process dowolnego z omówionych sygnałów i wykonywania funkcji wywołania zwrotnego. Przyjmujemy założenie, że utworzyłeś serwer, ale po naciśnięciu klawiszy Ctrl+C następuje zakończenie jego działania. Nie jest to eleganckie, a dodatkowo wszelkie połączenia oczekujące zostaną po prostu usunięte. Rozwiązaniem jest przechwycenie sygnału SIGINT, wstrzymanie akceptowania nowych połączeń, umożliwienie zakończenia istniejących i dopiero wówczas zakończenie działania procesu. Odbywa się to przez nasłuchiwanie process.on('SIGINT', ...). Nazwa emitowanego zdarzenia jest taka sama jak nazwa sygnału: process.on('SIGINT', function () { console.log('Przechwycono naciśnięcie Ctrl+C!'); server.close();

});

Teraz po naciśnięciu kombinacji Ctrl+C na klawiaturze sygnał SIGINT zostanie wysłany z powłoki do procesu Node i zamiast natychmiastowego zakończenia działania procesu spowoduje wykonanie zdefiniowanego wywołania zwrotnego. Ponieważ domyślne zachowanie w większości aplikacji to zakończenie działania procesu, zwykle dobrym pomysłem jest zrobienie tego samego we własnej procedurze obsługi sygnału SIGINT po przeprowadzeniu wszelkich niezbędnych operacji. W omawianym przykładzie wystarczające jest wstrzymanie akceptacji nowych połączeń przez serwer. Pomimo braku prawidłowych sygnałów takie rozwiązanie działa również w Windows, ponieważ Node obsługuje odpowiednie akcje Windows i symuluje sztuczne sygnały w Node. Tę samą technikę można zastosować do przechwycenia dowolnego sygnału systemu UNIX wysyłanego do procesu Node. Lista sygnałów UNIX została wymieniona w artykule Wikipedii na stronie http://en.wikipedia.org/wiki/Unix_signal#POSIX_signals. Niestety, ogólnie rzecz biorąc, poza kilkoma symulowanymi (SIGINT, SIGBREAK, SIGHUP i SIGWINCH) sygnały nie działają w systemie Windows.

13.3.2. Użycie modułu filesystem M o duł fs zapewnia funkcje niezbędne do współpracy z systemem plików komputera, w którym uruchomiono Node. Większość funkcji to rodzaj mapowania typu „jeden do jednego” dla ich odpowiedników w języku C, ale istnieją również działające na wysokim poziomie abstrakcje, takie jak fs.readFile(), fs.writeFile(), klasy fs.ReadStream i fs.WriteStream, które zostały opracowane na bazie open(), read(), write() i close(). Niemal wszystkie funkcje działające na niskim poziomie są używane identycznie jak ich odpowiedniki w języku C. W rzeczywistości większość dokumentacji Node odsyła Cię do odpowiednich stron podręcznika systemowego man wyjaśniających działanie dopasowanych funkcji C. Funkcje działające na niskim poziomie można bardzo łatwo zidentyfikować, ponieważ zawsze mają synchroniczny odpowiednik. Na przykład fs.stat() i fs.statSync() to działające na niskim poziomie odpowiedniki funkcji języka C o nazwie stat(). Funkcje synchroniczne w Node.js. Jak już wiesz, API Node to przede wszystkim funkcje asynchroniczne, które nigdy nie blokują pętli zdarzeń. Dlaczego więc dołączone są synchroniczne wersje funkcji systemu plików? Odpowiedź jest prosta: funkcja require() w Node jest synchroniczna i została zaimplementowana za pomocą funkcji modułu fs, a więc synchroniczne odpowiedniki są niezbędne. Funkcje synchroniczne w Node powinny być używane jedynie podczas uruchamiania lub początkowego wczytania modułu, a nigdy później.

Teraz zapoznamy Cię z przykładami pracy z systemem plików.

Przenoszenie pliku Na pozór prostym i dość często przeprowadzanym zadaniem podczas pracy z systemem plików jest przenoszenie pliku między katalogami. Na platformie UNIX do tego celu używa się polecenia mv, natomiast w Windows — polecenia move. Wydaje się, że przeprowadzenie tej operacji w Node powinno być proste. Cóż, jeśli przejrzysz dokumentację modułu fs (http://nodejs.org/api/fs.html), to zauważysz brak funkcji w stylu fs.move(). Jednak istnieje funkcja fs.rename(), która tak naprawdę wykonuje to samo zadanie. Doskonale! Nie tak szybko… Funkcja fs.rename() jest mapowana bezpośrednio na funkcję C o nazwie rename(). Jednak wadą wymienionej funkcji jest to, że nie działa między dwoma różnymi urządzeniami fizycznymi (na przykład między dwoma dyskami twardymi). Dlatego też poniższy fragment kodu nie działa zgodnie z oczekiwaniami i spowoduje zgłoszenie błędu typu EXDEV: fs.rename('C:\\hello.txt', 'D:\\hello.txt', function (err) { // err.code === 'EXDEV' });

Co można zrobić w takiej sytuacji? Nadal można utworzyć nowy plik na dysku D:\, odczytać zawartość pliku z dysku C:\ i skopiować ją przez sieć. Można przygotować zoptymalizowaną funkcję move() wywołującą fs.rename(), gdy istnieje taka możliwość, i kopiującą plik między urządzeniami przy użyciu fs.ReadStream i fs.WriteStream. Przykładową implementację funkcji move() przedstawiono w listingu 13.8. Listing 13.8. Funkcja move(), która zmienia nazwę pliku (o ile to możliwe) lub go kopiuje var fs = require('fs'); module.exports = function move (oldPath, newPath, callback) { fs.rename(oldPath, newPath, function (err) { Wywołanie fs.rename() w nadziei, że zadziała prawidłowo. if (err) { if (err.code === 'EXDEV') { W przypadku wystąpienia błędu EXDEV trzeba zastosować kopiowanie. copy(); } else { callback(err); Niepowodzenie wywołania i zgłoszenie tego faktu wywołującemu, jeśli wystąpi inny rodzaj błędu. } return; }

callback(); Jeżeli funkcja fs.rename() zadziałała prawidłowo, w tym momencie zadanie jest wykonane. }); function copy() { var readStream = fs.createReadStream(oldPath); Odczyt pliku źródłowego i jego potokowanie do pliku docelowego. var writeStream = fs.createWriteStream(newPath); readStream.on('error', callback); writeStream.on('error', callback); readStream.on('close', function () { fs.unlink(oldPath, callback); Usunięcie pliku źródłowego po jego wcześniejszym skopiowaniu. }); readStream.pipe(writeStream); } }

Moduł można przetestować bezpośrednio w interfejsie REPL Node, na przykład: $ node > var move = require('./copy') > move('copy.js', 'copy.js.bak', function (err) { if (err) throw err })

Zwróć uwagę, że funkcja copy() działa jedynie z plikami, a nie z katalogami. Aby działała również z katalogami, konieczne jest sprawdzenie w pierwszej kolejności, czy podana ścieżka dostępu prowadzi do katalogu. Jeśli tak, wówczas wywoływane są funkcje fs.readdir() i fs.mkdir(). Obsługę katalogów przez funkcję copy() możesz teraz zaimplementować samodzielnie. Kody błędów modułu fs. Moduł fs zwraca standardowe nazwy systemu UNIX dla kodów błędów systemu plików (http://www.gnu.org/software/libc/manual/html_node/Error-Codes.html), a więc znajomość wspomnianych nazw jest wymagana. Te nazwy są normalizowane przez bibliotekę libuv nawet w Windows, a więc aplikacja musi sprawdzać za każdym razem tylko jeden kod błędu. Zgodnie z informacjami zamieszczonymi w dokumentacji GNU, błąd EXDEV występuje podczas wykrycia „próby utworzenia nieprawidłowego dowiązania między systemami plików”.

Monitorowanie katalogu lub pliku pod kątem zmian Funkcja fs.watchFile() jest dostępna od dawna. Na pewnych platformach jej działanie jest kosztowne, ponieważ stosuje próbkowanie w celu sprawdzenia, czy plik został zmieniony. Oznacza to wywołanie stat() dla pliku, odczekanie krótkiego zakresu czasu, następnie ponowne wywołanie stat(). Cały proces jest przeprowadzany nieustannie w pętli. Po wykryciu zmiany w pliku następuje

wywołanie zdefiniowanej funkcji. Przyjmujemy założenie, że tworzony jest moduł rejestrujący zmiany wprowadzane w pliku dziennika zdarzeń system.log. Konieczne jest zdefiniowanie funkcji wywołania zwrotnego, która będzie wykonywana po wykryciu modyfikacji wymienionego pliku: var fs = require('fs'); fs.watchFile('/var/log/system.log', function (curr, prev) { if (curr.mtime.getTime() !== prev.mtime.getTime()) { console.log('Plik "system.log" został zmodyfikowany'); } });

Zmienne curr i prev to bieżący i poprzedni obiekt fs.Stat — powinny mieć różne znaczniki czasu dla tego samego dołączonego do nich pliku. W omawianym przykładzie porównywane są wartości mtime, ponieważ moduł ma informować jedynie o modyfikacji pliku, a nie o uzyskaniu do niego dostępu. F unk c j a fs.watch() została wprowadzona w Node v0.6. Jak wcześniej wspomniano, jest bardziej zoptymalizowana niż fs.watchFile(), ponieważ podczas monitorowania plików używa rodzimego dla platformy API powiadomień o zmianie pliku. Dlatego też wymieniona funkcja może monitorować katalog pod kątem zmian w dowolnym ze znajdujących się w nim plików. W praktyce funkcja fs.watch() jest mniej niezawodna niż fs.watchFile(), co wynika z różnic między stosowanymi przez poszczególne platformy mechanizmami monitorowania plików. Na przykład parametr filename nie jest zgłaszany w systemie OS X podczas monitorowania katalogu, a firma Apple może to zmienić w kolejnych wydaniach systemu OS X. W dokumentacji Node znajdziesz listę tego rodzaju zastrzeżeń (http://nodejs.org/api/fs.html#fs_caveats).

Użycie opracowanych przez społeczność modułów fstream i filed Jak się przekonałeś, moduł fs — podobnie jak całe podstawowe API Node — działa jedynie na niskim poziomie. Oznacza to sporo miejsca na innowacje i tworzenie abstrakcji na jego podstawie. Kolekcja aktywnych modułów Node w repozytorium npm zwiększa się każdego dnia i jak możesz się domyślać, istnieją również pewne doskonałe rozwiązania zbudowane w oparciu o moduł fs. Na przykład opracowany przez Isaaca Schluetera moduł fstream (https://github.com/isaacs/fstream) to jeden z podstawowych komponentów samego menedżera npm. Wymieniony moduł jest interesujący, ponieważ zaistniał jako część menedżera npm, a następnie został wyodrębniony do postaci samodzielnego modułu, ponieważ jego funkcjonalność ogólnego przeznaczenia okazała się użyteczna dla wielu różnego rodzaju narzędzi powłoki i skryptów

przeznaczonych dla administratorów systemów. Jedną z fantastycznych funkcji m o d u ł u fstream jest bezproblemowa obsługa uprawnień i dowiązań symbolicznych, czym moduł zajmuje się domyślnie podczas kopiowania plików i katalogów. Dzięki modułowi fstream można wykonać operację odpowiadającą wywołaniu cp rp katalog_źródłowy katalog_docelowy (rekurencyjne kopiowanie katalogu wraz z zawartością, a także transfer właściciela i uprawnień) przez po prostu potokowanie egzemplarza Reader do egzemplarza Writer. W przedstawionym poniżej fragmencie kodu wykorzystano również możliwości modułu fstream w zakresie filtrowania w celu warunkowego wykluczania plików na podstawie funkcji wywołania zwrotnego: fstream .Reader("ścieżka/do/katalogu") .pipe(fstream.Writer({ path: "ścieżka/do/innego/katalogu", filter: isValid ) // Sprawdzenie, czy plik jest przeznaczony do zapisania // oraz czy ewentualnie może być nadpisany. function isValid () { // Zignorowanie plików tymczasowych edytorów tekstu, na przykład TextMate. return this.path[this.path.length - 1] !== '~'; }

Opracowany przez Mikeala Rogersa moduł filed (https://github.com/mikeal/filed) to inny ważny moduł, ponieważ Mikeal jest również autorem niezwykle popularnego modułu request. Wymienione moduły spopularyzowały nowy rodzaj kontroli przepływu egzemplarzy Stream: nasłuchiwanie zdarzenia pipe i podjęcie odpowiedniego działania na podstawie potokowanych danych (lub miejsca ich potokowania). Aby poznać potężne możliwości związane z przedstawionym podejściem, spójrz, jak za pomocą modułu filed i jednego wiersza kodu można zmienić zwykły serwer HTTP na w pełni wyposażony serwer plików statycznych: http.createServer(function (req, res) { req.pipe(filed('ścieżka/do/plików/statycznych')).pipe(res); });

Powyższy kod zajmuje się wysłaniem nagłówka Content-Length wraz z odpowiednimi nagłówkami dotyczącymi buforowania. W przypadku gdy przeglądarka internetowa posiada buforowaną wersję pliku, na żądanie HTTP moduł filed udziela odpowiedzi o kodzie 304 (niezmodyfikowany), pomijając kroki związane z otworzeniem i odczytem pliku z dysku. To są te rodzaje optymalizacji, które działają ze zdarzeniem pipe, ponieważ egzemplarz filed ma

dostęp do obiektów req i res żądania HTTP. W ten sposób przedstawiliśmy dwa przykłady opracowanych przez społeczność dobrych modułów rozszerzających moduł bazowy fs o nowe możliwości i udostępniających piękne API. Warto pamiętać, że modułów jest znacznie więcej. Polecenie npm search do doskonały sposób na wyszukiwanie opublikowanych modułów przeznaczonych do wykonywania określonego zadania. Przyjmujemy założenie, że chcesz znaleźć jeszcze inny moduł ułatwiający proces kopiowania plików z jednej lokalizacji do drugiej. Wydanie polecenia npm search copy powinno przynieść wiele użytecznych wyników. Kiedy znajdziesz opublikowany moduł wyglądający interesująco, zawsze możesz wydać polecenie npm info nazwa-modułu, aby uzyskać więcej informacji na temat modułu, między innymi jego opis, adres strony domowej i opublikowane wersje. Pamiętaj o jednym: w przypadku danego zadania istnieje duże prawdopodobieństwo, że ktoś już próbował rozwiązać problem za pomocą modułu npm, i dlatego zawsze przed przystąpieniem do tworzenia własnego kodu od podstaw sprawdź, czy nie istnieje odpowiedni moduł w npm.

13.3.3. Tworzenie procesów zewnętrznych Node oferuje moduł child_process przeznaczony do tworzenia procesów potomnych w ramach serwera Node lub skryptu. Istnieją dwa API przeznaczone do wymienionego celu: działające na wysokim poziomie exec() oraz działające na niskim poziomie spawn(). W zależności od potrzeb każde z wymienionych API może być odpowiednie. Ponadto mamy jeszcze udostępniany przez samo Node specjalny sposób tworzenia procesu potomnego za pomocą wbudowanego kanału IPC o nazwie fork(). Wszystkie te funkcje są przeznaczone do użycia w różnych przypadkach: cp.exec() — działające na wysokim poziomie API przeznaczone do tworzenia poleceń i buforowania wyniku operacji w wywołaniu zwrotnym. cp.spawn() — działające na niskim poziomie API przeznaczone do tworzenia pojedynczych poleceń w obiekcie ChildProcess. cp.form() — specjalny sposób tworzenia dodatkowego procesu Node za pomocą wbudowanego kanału IPC. Powyższe API przeanalizujemy po kolei. Wady i zalety procesów potomnych. Istnieją wady i zalety użycia procesów potomnych. Oczywistą wadą jest konieczność instalacji w komputerze użytkownika uruchamianego programu, co czyni go zależnym od aplikacji.

Alternatywne rozwiązanie polega na użyciu języka JavaScript do wykonania zadań przeznaczonych dla procesu potomnego. Dobrym przykładem jest tutaj polecenie npm, które początkowo używało systemowego polecenia tar do rozpakowywania pakietów Node. Takie rozwiązanie powodowało problemy wynikające z niezgodności między wersjami polecenia tar, a ponadto rzadko się zdarzało, aby w systemie Windows było zainstalowane polecenie tar. Wymienione czynniki spowodowały opracowanie narzędzia node-tar (https://github.com/isaacs/node-tar), które całkowicie utworzono w języku JavaScript. Nie są więc używane żadne procesy potomne. Z drugiej strony, użycie aplikacji zewnętrznej pozwala programiście na wykorzystanie użytecznego programu utworzonego w zupełnie innym języku programowania. Na przykład gm (http://aheckmann.github.io/gm/) to moduł używający potężnych bibliotek GraphicMagick i ImageMagick do przeprowadzania wszelkich operacji na obrazach oraz ich konwersji w aplikacji Node.

Buforowanie za pomocą cp.exec() wyników działania polecenia Działające na wysokim poziomie API cp.exec() jest użyteczne podczas wywoływania polecenia — programistę interesuje jedynie wynik końcowy, a nie kwestie związane z dostępem do danych z poziomu potomnych strumieni standardowego wejścia, gdy się pojawiają. To API pozwala na podanie pełnych sekwencji poleceń, między innymi zawierających wiele procesów potokowanych jeden do drugiego. Jednym z dobrych przykładów użycia API cp.exec() jest akceptowanie przeznaczonych do wykonania poleceń użytkownika. Przyjmujemy założenie o tworzeniu bota IRC. Polecenie ma zostać wykonane, gdy użytkownik wprowadzi tekst zaczynający się od kropki. Na przykład jeśli jako wiadomość IRC użytkownik wpisze .ls, wówczas nastąpi wykonanie polecenia ls i wyświetlenie danych wyjściowych w pokoju IRC. Jak pokazano w listingu 13.9, konieczne jest ustawienie pewnej opcji timeout, aby nigdy nie kończące się procesy były automatycznie zamykane po upłynięciu pewnego czasu. Listing 13.9. Użycie cp.exec() do uruchamiania za pomocą IRC bot poleceń podawanych przez użytkownika var cp = require('child_process'); room.on('message', function (user, message) { Obiekt room przedstawia połączenie z pokojem IRC (z pewnego teoretycznego modułu IRC). if (message[0] === '.') { Zdarzenie message jest emitowane przez każdą wiadomość IRC wysyłaną do pokoju. var command = message.substring(1); cp.exec(command, { timeout: 15000 }, Sprawdzenie, czy treść wiadomości rozpoczyna się od kropki.

function (err, stdout, stderr) { if (err) { Utworzenie procesu potomnego i buforowanie w wywołaniu zwrotnym wyniku przez Node. Czas upływu ważności wynosi 15 sekund. room.say( 'Błąd podczas wykonywania polecenia "' + command + '": ' + err.message ); room.say(stderr); } else { room.say('Wykonywanie polecenia zakończone: ' + command); room.say(stdout); } } ); } });

Repozytorium npm zawiera jeszcze inne dobre moduły implementujące protokół IRC. Jeżeli faktycznie chcesz utworzyć bota IRC, powinieneś skorzystać z jednego z istniejących modułów (do popularnych zaliczają się irc i irc-js). Kiedy zachodzi potrzeba buforowania danych wyjściowych polecenia, ale chciałbyś użyć Node do automatycznego neutralizowania argumentów, wówczas możesz skorzystać z funkcji execFile(). Wymieniona funkcja pobiera cztery argumenty zamiast trzech. Przekazujesz jej pliki wykonywalne przeznaczone do uruchomienia oraz tablicę argumentów, z którymi mają być wykonane. Takie rozwiązanie jest użyteczne podczas przyrostowego tworzenia argumentów przeznaczonych do użycia przez proces potomny: cp.execFile('ls', [ '-l', process.cwd() ], function (err, stdout, stderr) { if (err) throw err; console.error(stdout); });

Tworzenie poleceń za pomocą interfejsu Stream i cp.spawn() Oferowane przez Node i działające na niskim poziomie API przeznaczone do tworzenia procesów potomnych to cp.spawn(). Ta funkcja różni się od cp.exec(), ponieważ zwraca obiekt ChildProcess, z którym można współpracować. Zamiast przekazywać cp.spawn() pojedynczą funkcję wywołania zwrotnego, gdy proces potomny zakończy działanie, cp.spawn() pozwala na interakcje z poszczególnymi strumieniami standardowego wejścia procesu potomnego. Najprostszy sposób użycia

cp.spawn()

przedstawia się następująco:

var child = cp.spawn('ls', [ '-l' ]); // Standardowe wyjście to zwykły egzemplarz Stream, który emituje zdarzenia 'data', // 'end' itd. child.stdout.pipe(fs.createWriteStream('ls-result.txt')); child.on('exit', function (code, signal) { // Zdarzenie emitowane po zakończeniu działania przez proces potomny. });

Pierwszy argument wskazuje program do uruchomienia. To może być pojedyncza nazwa programu, która zostanie wyszukana w lokalizacjach wskazanych przez aktualną wartość zmiennej środowiskowej PATH lub też bezwzględna ścieżka dostępu do programu. Drugim argumentem jest tablica ciągów tekstowych będących argumentami, z którymi zostanie uruchomiony proces. Domyślnie obiekt ChildProcess zawiera trzy wbudowane egzemplarze Stream, z którymi tworzone skrypty będą współpracowały: child.stdin — to zapisywalny obiekt Stream, który przedstawia standardowe wejście procesu potomnego. child.stdout — to odczytywalny obiekt Stream, który przedstawia standardowe wyjście procesu potomnego. child.stderr — to odczytywalny obiekt Stream, który przedstawia standardowe wyjście błędów procesu potomnego. Wymienione strumienie można wykorzystać w dowolny sposób, na przykład potokować je do pliku lub gniazda bądź też do innego rodzaju zapisywalnego strumienia. Jeśli chcesz, możesz je nawet zupełnie zignorować. Inne interesujące zdarzenie występujące w obiekcie ChildProcess to exit, które jest wywoływane po zakończeniu działania procesu i powiązanych z nim obiektów strumieni. Dobrym przykładem modułu zapewniającym abstrakcję użycia cp.spawn() w postaci użytecznej funkcjonalności jest node-cgi (https://github.com/TooTallNate/node-cgi). Pozwala on na ponowne użycie starych skryptów CGI (ang. Common Gateway Interface) w serwerach HTTP Node. CGI był standardem udzielania odpowiedzi na żądania HTTP przez wywoływanie skryptów CGI jako procesów potomnych serwera HTTP wraz ze specjalnymi zmiennymi środowiskowymi, które opisywały żądanie. Jako przykład poniżej przedstawiono skrypt CGI używający sh (powłoki) jako interfejsu CGI: #!/bin/sh echo "Status: 200" echo "Content-Type: text/plain"

echo echo "Witaj, $QUERY_STRING"

Jeżeli skrypt zostanie zapisany pod nazwą hello.cgi (nie zapomnij o wydaniu polecenia chmod +x hello.cgi, aby plik był wykonywalny), wówczas łatwo można wywołać skrypt jako logikę udzielania odpowiedzi na żądania HTTP. W serwerze HTTP wystarczy do tego celu pojedynczy wiersz kodu: var http = require('http'); var cgi = require('cgi'); var server = http.createServer( cgi('hello.cgi') ); server.listen(3000);

Po przygotowaniu serwera, kiedy żądanie HTTP dotrze do tego serwera, moduł node-cgi obsłuży je, wykonując dwa zadania: Za pomocą cp.spawn() uruchomienie skryptu hello.cgi jako nowego procesu potomnego. Przekazanie nowemu procesowi informacji o bieżącym żądaniu HTTP przy wykorzystaniu własnego zbioru zmiennych środowiskowych. S k r y p t hello.cgi używa jednej charakterystycznej dla CGI zmiennej środowiskowej QUERY_STRING, która zawiera ciąg tekstowy zapytania z adresu URL żądania. Ten ciąg tekstowy będzie wykorzystany w odpowiedzi, która zostanie umieszczona w danych wyjściowych skryptu. Jeżeli uruchomisz przygotowany serwer i wykonasz do niego żądanie HTTP za pomocą polecenia curl, wtedy otrzymasz wynik podobny do poniższego: $ curl http://localhost:3000/?Natalia Witaj, Natalia

Istnieje wiele użytecznych możliwości wykorzystania procesów potomnych w Node, a omówiony moduł node-cgi to tylko jedna z nich. Kiedy przygotowanego serwera lub aplikacji zaczniesz używać do celów, w których zostały opracowane, wówczas na pewnym etapie niewątpliwie znajdziesz odpowiednie zastosowanie dla procesu potomnego.

Rozkład obciążenia za pomocą cp.fork() Ostatnie API oferowane przez moduł child_process to specjalny sposób tworzenia nowych procesów Node za pomocą wbudowanego w Node kanału IPC. Ponieważ zawsze będzie tworzony nowy proces samego Node, pierwszym argumentem przekazywanym wywołaniu cp.fork() jest ścieżka dostępu do modułu Node.js przeznaczonego do wykonania. Podobnie jak jest w przypadku

cp.spawn(),

także

cp.fork()

zwraca obiekt ChildPro

cess.

Podstawowa różnica polega na dodaniu API przez kanał IPC: proces potomny ma funkcję child.send(message), a skrypt wywoływany przez fork() może nasłuchiwać zdarzeń process.on('message'). Przyjmujemy założenie, że w Node tworzymy serwer HTTP przeznaczony do obliczenia ciągu Fibonacciego. Tego rodzaju serwer możesz spróbować utworzyć w Node za pomocą kodu przedstawionego w listingu 13.10. Listing 13.10. Nieoptymalna implementacja ciągu Fibonacciego w Node.js var http = require('http'); function fib (n) { Obliczenie liczby w ciągu Fibonacciego. if (n < 2) { return 1; } else { return fib(n - 2) + fib(n - 1); } } var server = http.createServer(function (req, res) { var num = parseInt(req.url.substring(1), 10); res.writeHead(200); res.end(fib(num) + "\n"); }); server.listen(8000);

Jeżeli uruchomisz serwer za pomocą node fibonacci-native.js i wykonasz żądanie HTTP do http://localhost:8000, wówczas serwer będzie działał zgodnie z oczekiwaniami i obliczy ciąg Fibonacciego dla danej liczby. Taka operacja będzie jednak kosztowna i znacznie obciąży procesor. Ponieważ serwer Node jest jednowątkowy i będzie zajęty obliczaniem wyniku, inne żądania HTTP nie będą mogły być w tym czasie obsługiwane. Ponadto wykorzystany zostanie tylko jeden rdzeń procesora, a inne prawdopodobnie pozostaną nieaktywne. Z wymienionych powodów przedstawione rozwiązanie jest złe. Lepsze rozwiązanie polega na utworzeniu nowego procesu Node podczas każdego żądania HTTP i zleceniu procesowi potomnemu przeprowadzenia kosztownych obliczeń i podania ich wyniku. Wywołanie cp.fork() oferuje czysty interfejs do tego celu. Rozwiązanie będzie się składało z dwóch następujących plików: fibonacci-server.js — to skrypt serwera. fibonacci-calc.js — to skrypt procesu przeprowadzającego obliczenia.

Poniżej przedstawiono kod serwera: var http = require('http'); var cp = require('child_process'); var server = http.createServer(function(req, res) { var child = cp.fork(__filename, [ req.url.substring(1) ]); child.on('message', function(m) { res.end(m.result + '\n'); }); }); server.listen(8000);

Serwer używa wywołania cp.fork() do umieszczenia w oddzielnym procesie Node logiki odpowiedzialnej za obliczenie ciągu Fibonacciego. Wynik obliczeń zostanie przekazany procesowi nadrzędnemu za pomocą process.send(), jak przedstawiono w poniższym skrypcie fibonacci-calc.js: function fib(n) { if (n < 2) { return 1; } else { return fib(n - 2) + fib(n - 1); } } var input = parseInt(process.argv[2], 10); process.send({ result: fib(input) });

Teraz możesz uruchomić serwer za pomocą node wykonać żądanie HTTP do http://localhost:8000.

fibonacci-server.js

i ponownie

To jest doskonały przykład pokazujący, jak podział różnych komponentów aplikacji na wiele procesów może przynieść ogromne korzyści. Rozwiązanie oparte na cp.fork() zapewnia funkcje child.send() i child.on('message') przeznaczone do wysyłania i odbierania komunikatów z procesu potomnego. W ramach samego procesu potomnego dysponujesz funkcjami process.send() i process.on('message') przeznaczonymi do wysyłania i odbierania komunikatów z procesu nadrzędnego. Używaj wymienionych funkcji! Przechodzimy teraz do tematu, jakim jest opracowywanie w Node narzędzi działających w powłoce.

13.4. Tworzenie narzędzi powłoki

Inne zadanie często realizowane przez skrypty Node to tworzenie narzędzi działających w powłoce. Jak dotąd powinieneś znać już największe narzędzie powłoki zbudowane za pomocą Node — menedżer pakietów Node Package Manager, czyli polecenie npm. Ponieważ wymienione narzędzie jest menedżerem pakietów, przeprowadza wiele operacji systemu plików, tworzy nowe procesy potomne, a wszystko odbywa się za pomocą Node i jego asynchronicznego API. W ten sposób menedżer może instalować pakiety równolegle zamiast szeregowo, co przyśpiesza cały proces. Ponadto skoro w Node można utworzyć tak skomplikowane narzędzie powłoki, oznacza to, że za pomocą Node można zbudować dowolne narzędzie powłoki. W większości programów powłoki występuje potrzeba wykonywania tych samych zadań związanych z procesami, na przykład przetwarzania argumentów powłoki, odczytu danych ze standardowego wejścia, a także zapisu do standardowego wyjścia oraz obsługi błędów. W tym podrozdziale poznasz zadania najczęściej wykonywane podczas tworzenia programu powłoki, takie jak: Przetwarzanie argumentów powłoki. Praca ze strumieniami danych wejściowych i wyjściowych. Dodanie koloru do danych wyjściowych za pomocą skryptu ansi.js. Aby móc rozpocząć tworzenie wspaniałych programów powłoki, należy posiadać umiejętność odczytu argumentów, z którymi został uruchomiony program. Tym zagadnieniem zajmiemy się na kolejnych stronach.

13.4.1. Przetwarzanie argumentów podanych w powłoce Przetwarzanie argumentów to łatwy i prosty proces. Node oferuje właściwość process.argv będącą tablicą ciągów tekstowych, które są argumentami użytymi podczas wywołania Node. Pierwszym elementem tablicy jest plik wykonywalny Node, natomiast drugi to nazwa skryptu. Przetwarzanie i podejmowanie działań na podstawie wspomnianych argumentów wymaga przeprowadzenia iteracji przez elementy tablicy oraz sprawdzenia każdego argumentu. Aby to zademonstrować, utworzymy teraz prosty skrypt o nazwie args.js, którego zadaniem jest wyświetlenie zawartości tablicy process.argv. W większości przypadków dwa pierwsze elementy tablicy nie będą Cię interesowały i dlatego usuwamy je z danych wyjściowych za pomocą wywołania slice(): var args = process.argv.slice(2);

console.log(args);

Po wywołaniu skryptu bez argumentów tablica będzie pusta, ponieważ żadne argumenty dodatkowe nie zostały przekazane skryptowi: $ node args.js []

Jednak wywołanie skryptu z argumentami jan i kowalski powoduje, że tablica zawiera oczekiwane wartości w postaci ciągów tekstowych: $ node args.js jan kowalski [ 'jan', 'kowalski' ]

Ujęcie w nawias argumentów zawierających spacje powoduje — podobnie jak w każdej aplikacji powłoki — połączenie ich w pojedynczy argument. To nie jest funkcja Node, ale używanej powłoki (najczęściej bash na platformie UNIX i cmd.exe w Windows): $ node args.js "tobi jest zwierzakiem" [ 'tobi jest zwierzakiem' ]

Zgodnie z konwencją platformy UNIX każdy program powłoki powinien obsługiwać opcje -h i --help przez wyświetlenie informacji o sposobie użycia programu, a następnie zakończenie jego działania. W listingu 13.11 przedstawiono przykład użycia Array#forEach() do iteracji przez argumenty programu oraz przetworzenia ich za pomocą wywołania zwrotnego. Użycie wymienionych wcześniej opcji powoduje wyświetlenie informacji o sposobie użycia programu. Listing 13.11. Przetworzenie tablicy process.argv za pomocą Array#forEach() i bloku switch var args = process.argv.slice(2); Usunięcie z danych wyjściowych dwóch pierwszych elementów tablicy, którymi najczęściej nie jesteśmy zainteresowani. args.forEach(function (arg) { Iteracja przez argumenty i wyszukiwanie opcji -h lub -help. switch (arg) { case '-h': case '--help': printHelp(); break; Jeśli zachodzi potrzeba, umieść tutaj dodatkowe opcje. } }); function printHelp () { Wyświetlenie komunikatu o sposobie użycia programu, a następnie zakończenie jego działania. console.log(' Użycie:'); console.log(' $ WspaniałyProgram '); console.log(' Przykład:');

console.log(' $ WspaniałyProgram --wspaniała-opcja jeszcze-niezbyt.wspaniałe'); process.exit(0); }

B l o k switch można bardzo łatwo rozbudować o możliwość przetwarzania dodatkowych opcji. Moduły opracowane przez społeczność, na przykład commander.js, nopt, optimist i nomnom (wymieniono tutaj jedynie kilka dostępnych), przetwarzają opcje na własne sposoby. Dlatego też warto wiedzieć, że użycie bloku switch to nie jest jedyny możliwy sposób przetwarzania argumentów. Podobnie jak w wielu innych przypadkach, tak i tu nie istnieje jeden prawidłowy sposób wykonania danego zadania w programowaniu. Inne zadanie, z którym każdy program powłoki musi sobie radzić, to odczyt danych wejściowych ze standardowego wejścia i zapis strukturyzowanych danych wyjściowych do standardowego wyjścia. Przekonajmy się, jak można to zrobić w Node.

13.4.2. Praca ze standardowym wejściem i wyjściem Programy na platformie UNIX są najczęściej małe, samodzielne i skoncentrowane na wykonywaniu pojedynczego zadania. Poszczególne programy są łączone za pomocą potoków i wynik działania jednego jest przekazywany do kolejnego programu, aż do zakończenia łańcucha poleceń. Na przykład za pomocą standardowych poleceń systemu UNIX można pobrać listę unikatowych autorów z dowolnego repozytorium Git. Wymaga to połączenia poleceń git log, sort i uniq w następujący sposób: $ git log --format='%aN' | sort | uniq Mike Cantelon Nathan Rajlich TJ Holowaychuk

Wymienione polecenia działają jednocześnie, dane wyjściowe jednego procesu są używane jako dane wejściowe kolejnego i tak aż do końca łańcucha poleceń. Aby zaoferować mechanizm podobny do potokowania, Node dostarcza dwa obiekty Stream, które można wykorzystać w budowanych programach powłoki: process.stdin — obiekt ReadStream przeznaczony do odczytu danych wejściowych. process.stdout — obiekt WriteStream przeznaczony do zapisu danych wyjściowych. Sposób działania wymienionych obiektów jest podobny jak omówionych

wcześniej interfejsów strumieni.

Zapis danych wyjściowych za pomocą process.stdout Zapisywalnego strumienia process.stdout używasz za każdym razem, gdy wywoływana jest funkcja console.log(). Wewnętrznie wymieniona funkcja w y w o ł u j e process.stdout.write() po sformatowaniu argumentów danych wejściowych. Jednak funkcje console są przeznaczone bardziej do debugowania i analizowania obiektów. Kiedy zachodzi potrzeba zapisu w standardowym wyjściu strukturyzowanych danych, wtedy można bezpośrednio wywołać process.stdout.write(). Przyjmujemy założenie, że program nawiązuje połączenie z adresem HTTP URL, a następnie odpowiedź przekazuje do standardowego wyjścia. W takim kontekście doskonale sprawdza się Stream#pipe(), jak przedstawiono w poniższym fragmencie kodu: var http = require('http'); var url = require('url'); var target = url.parse(process.argv[2]); var req = http.get(target, function (res) { res.pipe(process.stdout); });

Doskonale! Niezwykle minimalna replika polecenia curl utworzona za pomocą jedynie sześciu wierszy kodu. Nie tak źle, prawda? Teraz zajmiemy się process.stdin.

Odczyt danych wejściowych za pomocą process.stdin Zanim będzie można odczytać dane ze standardowego wejścia, konieczne jest wywołanie process.stdin.resume() w celu wskazania, że skrypt jest zainteresowany danymi pochodzącymi ze standardowego wejścia. Działa ono podobnie jak każdy inny odczytywalny strumień, emituje zdarzenia data po otrzymaniu danych wyjściowych innego procesu oraz kiedy użytkownik naciśnie dowolny klawisz w oknie terminalu. W listingu 13.12 przedstawiono kod programu powłoki, który prosi użytkownika o podanie wieku. Na podstawie otrzymanych informacji program podejmuje decyzję o dalszym sposobie działania. Listing 13.12. Program, który prosi użytkownika o podanie wieku var requiredAge = 18; Ustawienie granicy wieku. process.stdout.write('Proszę podać wiek: '); Przygotowanie pytania dla użytkownika. process.stdin.setEncoding('utf8'); Określenie, że standardowe wejście ma emitować ciągi tekstowe UTF-8 zamiast buforów. process.stdin.on('data', function (data) {

var age = parseInt(data, 10); Przetworzenie danych na postać liczby. if (isNaN(age)) { Jeżeli wiek podany przez użytkownika jest mniejszy niż 18 lat, należy wyświetlić komunikat informujący go, aby powrócił do programu za kilka lat. console.log('%s nie jest poprawną liczbą!', data); } else if (age < requiredAge) { Jeżeli użytkownik nie podał prawidłowej liczby, należy wyświetlić odpowiedni komunikat. console.log('Musisz mieć co najmniej %d lat, aby uzyskać dostęp do programu. ' + 'Wróć za %d lat', requiredAge, requiredAge - age); } else { enterTheSecretDungeon(); Po spełnieniu wcześniejszych warunków można kontynuować wykonywanie programu. } process.stdin.pause(); Oczekiwanie na pojedyncze zdarzenie data przed zamknięciem standardowego wejścia. }); process.stdin.resume(); Wywołanie funkcji resume() w celu rozpoczęcia odczytu, ponieważ process.stdin na początku znajduje się w stanie wstrzymania. function enterTheSecretDungeon () { console.log('Witamy w Programie :)'); }

Rejestracja danych diagnostycznych za pomocą process.stderr W każdym procesie Node istnieje również zapisywalny strumień process.stderr, który działa dokładnie tak samo jak process.stdout, ale dane przekazuje do standardowego wyjścia błędów. Ponieważ jest ono zarezerwowane zwykle dla debugowania, a nie dla wysyłania strukturalnych danych lub potokowania, ogólnie rzecz biorąc, będziesz używał funkcji console.error(), zamiast uzyskiwać bezpośredni dostęp do process.stderr. Po poznaniu standardowych strumieni wejścia-wyjścia w Node dysponujesz wiedzą niezbędną przy tworzeniu programów powłoki. Przystępujemy więc do zbudowania czegoś nieco bardziej kolorowego.

13.4.3. Dodanie koloru do danych wyjściowych Wiele narzędzi powłoki używa kolorowego tekstu w celu ułatwienia użytkownikom odróżniania informacji na ekranie. Node również stosuje tę funkcjonalność w interfejsie REPL, podobnie jak menedżer npm na różnych poziomach rejestracji danych. Użycie koloru to przydatny dodatek, z którego łatwo może skorzystać praktycznie każdy program powłoki. Dodanie koloru to

łatwe zadanie, zwłaszcza w przypadku użycia jednego z modułów opracowanych przez społeczność.

Tworzenie i zapis znaków sterujących ANSI Kolory w terminalu są generowane za pomocą znaków sterujących ANSI (ang. American National Standards Institute). Wspomniane znaki sterujące to sekwencje zwykłego tekstu przekazywane do standardowego wyjścia i mające znaczenie specjalne dla terminalu — mogą zmienić kolor tekstu, położenie kursora, odtworzyć dźwięk itd. Zaczniemy od prostego przykładu. Aby skrypt wyświetlił słowo witaj w kolorze zielonym, można użyć pojedynczego wywołania console.log(): console.log('\033[32mwitaj\033[39m');

Jeżeli przyjrzysz się dokładnie powyższemu wywołaniu, słowo witaj dostrzeżesz w środku ciągu tekstowego, wśród dziwnych znaków wokół. W pierwszej chwili to może wydawać się dziwne, ale tak naprawdę jest to prosty mechanizm. Na rysunku 13.4 pokazano podział ciągu tekstowego witaj w kolorze zielonym na trzy oddzielne części.

Rysunek 13.4. Wyświetlenie słowa „witaj” w kolorze zielonym za pomocą znaków sterujących ANSI

Istnieje znacznie więcej znaków sterujących rozpoznawanych przez terminale, ale większość programistów ma ważniejsze rzeczy do zrobienia niż zapamiętywanie ich. Na szczęście społeczność Node okazuje się pomocna nawet w takiej sytuacji — opracowała wiele modułów, między innymi colors.js, clicolor.js i ansi.js, dzięki którym użycie koloru w programie powłoki jest niezwykle łatwe. Kody ANSI w Windows. Technicznie rzecz biorąc, system Windows i jego wiersz poleceń (cmd.exe) nie obsługują znaków sterujących ANSI. Na szczęście dla nas Node interpretuje znaki sterujące w Windows, gdy skrypt przekazuje je do standardowego wyjścia, a następnie wywołuje odpowiednie funkcje Windows zapewniające możliwość osiągnięcia tego samego wyniku. Warto o tym wiedzieć, choć jednocześnie nie musisz się nad tym zastanawiać podczas tworzenia aplikacji Node.

Formatowanie koloru tekstu za pomocą ansi.js Spójrz na moduł ansi.js (https://github.com/TooTallNate/ansi.js), który można zainstalować za pomocą npm install ansi. Wymieniony moduł jest dobrym rozwiązaniem, ponieważ to bardzo cienka warstwa na górze zwykłych znaków sterujących ANSI, oferująca ogromną elastyczność w porównaniu z innymi modułami dodającymi kolor (działają one z pojedynczym ciągiem tekstowym). Za pomocą modułu ansi.js można zdefiniować tryby (na przykład „pogrubienie”) dla strumieni. Będą one stosowane aż do wywołania reset(). Warto w tym miejscu dodać, że ansi.js to pierwszy moduł obsługujący 256 kolorów terminala. Potrafi również konwertować kody kolorów CSS (takie jak #FF0000) na kody kolorów ANSI. M o duł ansi.js działa wraz z koncepcją kursora, który tak naprawdę jest opakowaniem dla egzemplarza zapisywalnego strumienia i zawiera wiele wygodnych funkcji przeznaczonych do umieszczania kodów ANSI w strumieniu, a ponadto obsługuje łączenie w łańcuchy. W celu wyświetlenia słowa witaj w kolorze zielonym za pomocą modułu ansi.js można użyć poniższego fragmentu kodu: var ansi = require('ansi'); var cursor = ansi(process.stdout); cursor .fg.green() .write('witaj') .fg.reset() .write('\n');

Jak możesz się przekonać, w celu użycia modułu ansi.js najpierw trzeba utworzyć egzemplarz cursor na podstawie zapisywalnego strumienia. Ponieważ interesuje nas zastosowanie koloru w danych wyjściowych programu, można przekazać process.stdout jako zapisywalny strumień używany przez egzemplarz coursor. Po uzyskaniu egzemplarza coursor można już wywołać dowolną z dostarczonych metod przeznaczonych do zmiany sposobu generowania w terminalu tekstu danych wyjściowych. W omawianym przykładzie wynik odpowiada wcześniej przedstawionemu wywołaniu console.log(): cursor.fg.green() — ustawienie koloru tekstu na zielony. cursor.write('witaj') — wyświetlenie w terminalu słowa witaj w kolorze zielonym. cursor.fg.reset() — przywrócenie domyślnego koloru tekstu. cursor.write('\n') — umieszczenie znaku nowego wiersza.

Programowe dopasowanie danych wyjściowych za estetyczny interfejs przeznaczony do zmiany kolorów.

pomocą

cursor

oferuje

Formatowanie koloru tła za pomocą ansi.js Moduł ansi.js pozwala również na zmianę koloru tła. Aby ustawić kolor tła zamiast tekstu, w wywołaniu należy fragment fg zastąpić przez bg. Na przykład ustawienie tła w kolorze czerwonym wymaga wywołania cursor.bg.red(). Przygotujemy teraz mały program wyświetlający w terminalu kolorowe informacje dotyczące tytułu i autorów niniejszej książki, jak pokazano na rysunku 13.5. Jak pokazano na rysunku, kod przeznaczony do wyświetlenia danych wyjściowych w kolorze jest nieco rozległy, ale łatwy. Każda funkcja jest mapowana bezpośrednio na znak sterujący umieszczany w strumieniu. Kod przedstawiony w listingu 13.13 składa się z dwóch wierszy inicjalizacyjnych oraz naprawdę długiego łańcucha wywołań funkcji, które ostatecznie umieszczają znaki sterujące koloru i ciągi tekstowe w pro cess.stdout. Kody pozwalające na zdefiniowanie koloru to tylko jedna z kluczowych funkcji modułu ansi.js. Nie przedstawiliśmy tutaj możliwości w zakresie umieszczania kursora, odtwarzania dźwięku bądź też ukrywania i wyświetlania kursora. Więcej informacji na ten temat i przykłady znajdziesz w dokumentacji modułu ansi.js.

Rysunek 13.5. Wynik uruchomienia skryptu ansi-title.js — tytuł książki i jej autorzy to informacje wyświetlone w różnych kolorach Listing 13.13. Prosty program, który wyświetla tytuł książki i jej autorów w różnych kolorach var ansi = require('ansi'); var cursor = ansi(process.stdout); cursor .reset() .write(' ')

.bold() .underline() .bg.white() .fg.black() .write('Node.js w akcji') .fg.reset() .bg.reset() .resetUnderline() .resetBold() .write(' \n') .fg.green() .write(' by:\n') .fg.cyan() .write('

Mike Cantelon\n')

.fg.magenta() .write('

TJ Holowaychuk\n')

.fg.yellow() .write('

Nathan Rajlich\n')

.reset()

13.5. Podsumowanie Node zaprojektowano przede wszystkim do wykonywania zadań związanych z operacjami wejścia-wyjścia, takimi jak tworzenie serwera HTTP. Jednak Node doskonale sprawdza się także w wielu innych zadaniach, między innymi podczas tworzenia programów powłoki dla serwera aplikacji, programu klienta nawiązującego połączenie z serwerem ASCII Star Wars, programu pobierającego i wyświetlającego dane statystyczne z serwerów giełdowych itd. Możliwości zastosowania są ograniczone jedynie Twoją wyobraźnią. Spójrz na menedżera npm i node-gyp — to dwa przykłady skomplikowanych narzędzi powłoki utworzonych za pomocą Node. To jednocześnie doskonałe przykłady, na podstawie których można się uczyć. W tym rozdziale wspomniano o kilku modułach opracowanych przez społeczność, które mogą pomóc podczas prac nad aplikacjami. W kolejnym rozdziale skoncentrujemy się na tym, jak wyszukiwać wspaniałe moduły opracowane przez społeczność Node. Dowiesz się również, jak podzielić się samodzielnie opracowanymi modułami w celu zebrania komentarzy i wprowadzenia usprawnień w modułach. Aspekt społecznościowy to naprawdę ekscytujący obszar!

Rozdział 14. Ekosystem Node W tym rozdziale: • Wyszukiwanie w internecie pomocy związanej z Node. • Współpraca nad Node poprzez serwis GitHub. • Publikowanie własnej pracy za pomocą Node Package Manager.

Aby w pełni móc wykorzystać możliwości programowania w Node, należy wiedzieć, gdzie szukać pomocy oraz jak dzielić się ze społecznością Node samodzielnie opracowanym kodem. Prace nad Node i powiązanymi projektami odbywają się podobnie jak w większości społeczności oprogramowania open source, czyli na zasadzie współpracy przez internet wielu osób. Programiści współpracują ze sobą, przekazując sobie i przeglądając kod, tworząc dokumentację projektu i zgłaszając błędy. Kiedy programiści są gotowi do wydania nowej wersji Node, zostaje ona opublikowana w oficjalnej witrynie Node. Po utworzeniu przez osoby trzecie modułu wartego wydania można go umieścić w repozytorium npm, dzięki czemu staje się łatwy do instalacji przez innych użytkowników. Zasoby dostępne w internecie zawierają wszelkie informacje niezbędne do rozpoczęcia pracy z Node oraz z projektami powiązanymi. Na rysunku 14.1 pokazano, jak używać zasobów internetowych podczas prac programistycznych związanych z Node, dystrybucją i zapewnieniem pomocy technicznej. Zanim zaczniesz współpracę z innymi, będziesz prawdopodobnie szukał pomocy technicznej. Dlatego też w pierwszej kolejności dowiedz się, gdzie w internecie znajdziesz pomoc, gdy będzie Ci ona potrzebna.

Rysunek 14.1. Projekty związane z Node są tworzone wspólnie za pośrednictwem serwisu GitHub. Następnie są publikowane w repozytorium npm, a dokumentacja i pomoc techniczna są dostępne za pomocą zasobów internetowych

14.1. Dostępne w internecie zasoby dla programistów Node Ponieważ świat Node nieustannie się zmienia, najnowsze informacje dotyczące tej technologii znajdziesz w internecie. Do dyspozycji masz wiele witryn internetowych, grup dyskusyjnych, czatów i innych zasobów, w których można znaleźć potrzebne informacje.

14.1.1. Node i odniesienia do modułów W tabeli 14.1 wymieniono pewne znajdujące się w internecie zasoby związane z Node. Najużyteczniejsze z nich to strony internetowe zawierające omówienie API Node (strona domowa Node.js), a także informacje o opracowanych przez firmy trzecie modułach (strona domowa menedżera npm). Tabela 14.1. Użyteczne zasoby w internecie dotyczące Node Z asób

Adres URL

Strona główna Node.js

http://nodejs.org/

Aktualna dokumentacja API Node

http://nodejs.org/api/

Blog Node

http://blog.nodejs.org/

Oferty pracy związane z Node

http://jobs.nodejs.org/a/jobs/find-jobs

Strona główna menedżera Node Package Manager (npm)

https://www.npmjs.org/

Jeśli próbujesz coś zaimplementować za pomocą Node lub dowolnego z wbudowanych modułów, to strona domowa Node stanowi nieoceniony zasób. Ta pokazana na rysunku 14.2 witryna internetowa zawiera pełną dokumentację frameworka Node i oferowanego przez niego API. Tutaj zawsze znajdziesz dokumentację dotyczącą najnowszej wersji Node. Oficjalny blog również zawiera informacje o nowościach wprowadzonych w Node oraz ogólnie komunikaty ważne dla społeczności Node. Strona domowa zawiera również oferty pracy.

Rysunek 14.2. Oprócz odnośników prowadzących do użytecznych zasobów powiązanych z Node witryna nodejs.org oferuje także dokumentację API dla każdej wydanej wersji Node

Jeśli szukasz funkcji opracowanych przez firmy trzecie, repozytorium npm to

pierwsze miejsce, które powinieneś odwiedzić. Pozwala na użycie słów kluczowych do przeszukiwania tysięcy modułów oferowanych przez npm. Jeżeli znajdziesz moduł, który chcesz sprawdzić, kliknij jego nazwę, co spowoduje wyświetlenie strony z informacjami szczegółowymi o module. Wspomniana strona zawiera między innymi łącze prowadzące do strony domowej projektu modułu oraz wszelkie użyteczne informacje o nim, na przykład wskazujące, jakie inne moduły zależą od danego, zależności danego modułu od innych, wersje zgodnych modułów oraz informacje o licencji. Wymienione wcześniej witryny internetowe nie dostarczają odpowiedzi na wszystkie pytania, które mogą się pojawić w zakresie użycia Node lub modułów opracowanych przez firmy trzecie. Zapoznaj się więc z innymi doskonałymi miejscami, w których można poprosić o pomoc.

14.1.2. Grupy Google Grupy Google zostały utworzone dla Node, menedżera npm oraz pewnych popularnych modułów i frameworków, między innymi Express, node-mogodb-native i Mongoose. Grupy Google to doskonałe miejsce do zadawania trudnych lub obszernych pytań. Na przykład jeśli masz problem ze znalezieniem sposobu na usunięcie dokumentów MongoDB za pomocą modułu node-mogodb-native, wówczas odpowiednie pytanie można zadać na grupie poświęconej wymienionemu modułowi (https://groups.google.com/forum/?fromgroups&hl=pl#!forum/nodemongodb-native) i przekonać się, czy ktokolwiek inny napotkał podobny problem. Jeżeli ktoś już spotkał się z podobnym problemem, Twoim kolejnym krokiem powinno być dołączenie do grupy Google i zadanie pytania. Możesz tworzyć długie posty, co jest przydatne w skomplikowanych pytaniach, ponieważ pozwala na dokładne wyjaśnienie istoty problemu. Nie istnieje centralna lista wymieniająca wszystkie grupy Google powiązane z Node. Możesz je znaleźć podane w dokumentacji projektu, choć najczęściej będziesz musiał sam wyszukać je w sieci. W tym celu w wyszukiwarce internetowej wystarczy podać wyrażenie „nazwa-modułu node.js grupa google” i sprawdzić, czy istnieje grupa dla wskazanego modułu. Wadą użycia grup Google jest często konieczność czekania kilka godzin lub nawet dni na uzyskanie odpowiedzi, w zależności od parametrów grupy Google. W przypadku prostych pytań, gdy szybko potrzebujesz odpowiedzi, powinieneś rozważyć skorzystanie z czatu internetowego, na którym z reguły szybko znajdziesz potrzebne informacje.

14.1.3. IRC

IRC (ang. Internet Relay Chat) utworzono w roku 1988 i choć niektórzy mogą uważać IRC za archaiczne rozwiązanie, ono nadal funkcjonuje i jest aktywnie wykorzystywane — to najlepszy sposób na szybkie uzyskanie odpowiedzi na pytania dotyczące oprogramowania open source. Pokoje IRC są nazywane kanałami, istnieją dla wielu modułów Node. Wprawdzie nigdzie nie znajdziesz listy kanałów IRC poświęconych Node, ale w dokumentacji modułu czasami umieszczane są informacje o poświęconym mu kanale IRC, o ile taki istnieje. Aby otrzymać odpowiedź na pytanie zadane na czacie, należy nawiązać połączenie z siecią IRC (http://chatzilla.hacksrus.com/faq/#connect), przejść do odpowiedniego kanału, a następnie po prostu zadać pytanie. Ze względu na szacunek wobec uczestników czatu przed zadaniem pytania warto sprawdzić, czy podobne pytanie nie pojawiło się wcześniej i czy nie została na nie już udzielona odpowiedź. Jeżeli jesteś początkującym użytkownikiem IRC, najłatwiejszym sposobem nawiązania połączenia będzie użycie klienta opartego na przeglądarce internetowej. Freenode, sieć IRC, w której znajduje się większość kanałów IRC poświeconych Node, oferuje klienta sieciowego pod adresem http://webchat.freenode.net. Aby przyłączyć się do czatu, w formularzu połączenia podaj nazwę użytkownika. Nie trzeba się rejestrować i można podać dowolną nazwę użytkownika. (Jeżeli ktokolwiek używa wybranej przez Ciebie nazwy użytkownika, na jej końcu zostanie umieszczony znak podkreślenia, aby umożliwić rozróżnianie użytkowników). Po kliknięciu przycisku Connect dołączysz do kanału. Po prawej stronie znajduje się pasek boczny, w którym wymieniono innych uczestników czatu.

14.1.4. Zgłaszanie problemów w serwisie GitHub Jeżeli projekt znajduje się w serwisie GitHub, wtedy innym miejscem pozwalającym na szukanie rozwiązania dla powstałych problemów jest strona zgłaszania problemów w projekcie GitHub. Aby przejść na tę stronę, należy najpierw wyświetlić stronę główną projektu w serwisie GitHub, a następnie kliknąć kartę Issues. Możesz skorzystać z pola wyszukiwania i spróbować odszukać inne problemy podobne do napotkanego przez Ciebie. Przykładową stronę przeznaczoną do zgłaszania problemów pokazano na rysunku 14.3.

Rysunek 14.3. W przypadku projektów umieszczonych w serwisie GitHub zgłoszenie błędu może być pomocne, jeśli sądzisz, że odkryłeś problem w kodzie projektu

Jeżeli nie znajdujesz informacji pomagających w rozwiązaniu problemu i uważasz, że może on być wynikiem błędu w kodzie projektu, kliknij przycisk New Issue na wyświetlonej stronie i dokładnie opisz problem. Po utworzeniu nowego zgłoszenia osoby zajmujące się projektem będą mogły odpowiedzieć na tej samej stronie i rozwiązać problem lub zadać dodatkowe pytania w celu lepszego zdiagnozowania problemu. Strona zgłaszania problemów w GitHub to nie forum pomocy technicznej. W zależności od projektu zadawanie na wspomnianej stronie ogólnych pytań z zakresu pomocy technicznej może być uznawane za nieodpowiednie. Dzieje się tak najczęściej, gdy dla projektu przygotowano inne formy udzielania pomocy technicznej, na przykład za pośrednictwem grup Google. Dobrym podejściem jest zapoznanie się z plikiem README projektu i sprawdzenie, czy istnieją jakiekolwiek wskazówki dotyczące zadania pytań z zakresu ogólnej pomocy technicznej. Teraz już wiesz, jak zgłaszać problemy związane z projektami GitHub. Przechodzimy więc do niezwiązanej z pomocą techniczną roli serwisu GitHub będącego miejscem współpracy większości osób zajmujących się rozwijaniem frameworka Node.

14.2. Serwis GitHub

Serwis GitHub to środek ciężkości świata oprogramowania open source i jednocześnie miejsce o znaczeniu krytycznym dla programistów Node. GitHub oferuje obsługę repozytoriów Git, czyli potężnego systemu kontroli wersji (ang. Version Control System , VCS). Serwis zawiera interfejs działający w przeglądarce internetowej i pozwalający na łatwe przeglądanie repozytoriów Git. Projekty typu open source mogą być umieszczone bezpłatnie w serwisie GitHub. Git. System kontroli wersji Git stał się najpopularniejszym wyborem dla projektów typu open source. To rozproszony system kontroli wersji, którego w przeciwieństwie do Subversion i innych tego rodzaju systemów można używać bez połączenia sieciowego z serwerem. Git powstał w roku 2005, zainspirowany przez własnościowy VCS o nazwie BitKeeper. Właściciel serwisu BitKeeper pozwolił na bezpłatne korzystanie z serwisu zespołowi pracującemu nad jądrem systemu Linux. Ten przywilej cofnął jednak, gdy nabrał podejrzeń, że członkowie zespołu próbują ustalić wewnętrzny sposób działania serwisu BitKeeper. Linus Torvalds, czyli twórca systemu Linux, postanowił utworzyć alternatywny system kontroli wersji, o podobnej funkcjonalności. Kilka miesięcy później system Git był już używany przez zespół pracujący nad jądrem systemu Linux. Poza hostingiem repozytoriów Git serwis GitHub oferuje stronę zgłaszania błędów, wiki, a także możliwość hostingu strony internetowej poświęconej projektowi. Ponieważ większość projektów Node w repozytorium npm jest przechowywana w serwisie GitHub, umiejętność posługiwania się tym serwisem pozwala na wykorzystanie pełni możliwości podczas programowania w Node. GitHub zapewnia wygodny sposób przeglądania kodu, sprawdzania nierozwiązanych błędów, a jeśli zachodzi potrzeba — także zgłaszania poprawek i opracowywania dokumentacji. Innym sposobem użycia GitHub jest obserwacja projektu. Obserwacja projektu oznacza otrzymywanie powiadomień o wprowadzeniu zmian w projekcie. Liczba osób obserwujących dany projekt jest często używana do określenia jego ogólnej popularności. Serwis GitHub może oferować potężne możliwości, ale jak z niego korzystać? Tego dowiesz się w kolejnym punkcie.

14.2.1. Rozpoczęcie pracy z GitHub Kiedy wpadniesz na pomysł utworzenia projektu lub opracowania modułu opartego na Node, wówczas warto utworzyć konto w serwisie GitHub (jeśli jeszcze tego nie zrobiłeś), aby zapewnić sobie łatwy dostęp do hostingu repozytoriów Git. Po utworzeniu konta i przeprowadzeniu jego konfiguracji będziesz mógł zacząć dodawać projekty, o czym dowiesz się w kolejnym

punkcie. Ponieważ GitHub wymaga systemu kontroli wersji Git, konieczne jest skonfigurowanie go przed rozpoczęciem pracy z GitHub. Na szczęście w serwisie GitHub znajdują się strony pomocy objaśniające prawidłową konfigurację Git na platformie Mac, Windows i Linux (https://help.github.com/articles/set-up-git). Po skonfigurowaniu Git możesz utworzyć konto GiHub, rejestrując się w witrynie internetowej serwisu i dostarczając klucz SSH. Wspomniany klucz SSH jest niezbędny do zapewnienia bezpiecznej współpracy z serwisem GitHub. W tym punkcie zostanie dokładnie omówiony proces konfiguracji. Zwróć uwagę, że omówione tutaj kroki trzeba wykonać tylko jednokrotnie, a nie podczas każdego dodawania projektu do GitHub.

Konfiguracja Git i rejestracja GitHub Aby można było używać serwisu GitHub, konieczne jest skonfigurowanie systemu kontroli wersji Git. Imię i nazwisko oraz adres e-mail podajesz za pomocą dwóch wymienionych poniżej poleceń: git config --global user.name "Jan Kowalski" git config --global user.email [email protected]

Teraz możesz przystąpić do rejestracji konta w GitHub. Przejdź na stronę rejestracji (https://github.com/join), wypełnij formularz i kliknij przycisk Create an account.

Dostarczenie GitHub klucza publicznego SSH Po rejestracji trzeba umieścić w serwisie GitHub klucz publiczny SSH (https://help.github.com/articles/generating-ssh-keys). Wymieniony klucz będzie używany do uwierzytelniania transakcji Git. Wykonaj wymienione poniżej kroki: 1. W przeglądarce internetowej przejdź na stronę https://github.com/settings/ssh. 2. Kliknij przycisk Add SSH Key. Na tym etapie dalsze postępowanie zależy od używanego systemu operacyjnego. GitHub wykryje Twój system operacyjny i wyświetli odpowiednie instrukcje.

14.2.2. Dodanie projektu do GitHub Po przygotowaniu konta w serwisie GitHub można przystąpić do dodawania projektów i przekazywania kodu do repozytoriów. W tym celu najpierw utwórz repozytorium GitHub dla projektu, co zostanie wkrótce omówione. Następnie w komputerze lokalnym utwórz repozytorium

Git, w którym będziesz umieszczał kod przed jego przekazaniem do GitHub. Cały proces został pokazany na rysunku 14.4. Pliki projektu można przejrzeć również za pomocą oferowanego przez GitHub interfejsu sieciowego.

Utworzenie repozytorium GitHub Utworzenie kroków:

repozytorium Git

wymaga

wykonania

wymienionych poniżej

1. W przeglądarce internetowej przejdź do serwisu GitHub i zaloguj się. 2. Przejdź na stronę https://github.com/new.

Rysunek 14.4. Kroki niezbędne podczas dodawania projektu Node do GitHub

3. Wypełnij wyświetlony formularz opisujący repozytorium i kliknij przycisk Create Repository. 4. GitHub utworzy puste repozytorium Git oraz stronę zgłaszania błędów dla projektu. 5. GitHub wyświetli kroki, jakie trzeba wykonać, aby użyć systemu kontroli wersji Git do umieszczenia kodu w nowym repozytorium. Warto rozumieć działanie poszczególnych kroków i dlatego zostaną tutaj omówione, a przykłady przedstawią podstawy użycia Git.

Konfiguracja pustego repozytorium Git W celu dodania przykładowego projektu do serwisu GitHub najpierw utworzymy prosty moduł dla Node. W omawianym przykładzie będzie to moduł zawierający logikę przeznaczoną do skracania adresów URL. Modułowi nadajemy nazwę node-elf. Rozpoczynamy więc od utworzenia katalogu tymczasowego dla projektu, wydając wymienione poniżej polecenia: mkdir -p ~/tmp/node-elf cd ~/tmp/node-elf

Aby użyć katalogu jako repozytorium Git, należy wydać poniższe polecenie, które spowoduje utworzenie katalogu o nazwie .git zawierającego metadane repozytorium: git init

Dodanie plików do repozytorium Git Po przygotowaniu pustego repozytorium możemy dodać do niego pewne pliki. W omawianym przykładzie to będzie plik zawierający logikę odpowiedzialną za skracanie adresów URL. Kod przedstawiony w listingu 14.1 umieść w pliku o nazwie index.js w utworzonym wcześniej katalogu. Listing 14.1. Moduł Node służący do skracania adresów URL exports.initPathData = function(pathData) { Funkcja inicjalizacyjna jest wywoływana niejawnie przez funkcje shorten() i expand(). pathData = (pathData) ? pathData : {}; pathData.count = (pathData.count) ? pathData.count : 0; pathData.map

= (pathData.map) ? pathData.map : {};

} exports.shorten = function(pathData, path) { Funkcja akceptuje ciąg tekstowy „ścieżki” i zwraca skrócony adres URL mapowany na tę ścieżkę. exports.initPathData(pathData); pathData.count++; pathData.map[pathData.count] = path; return pathData.count.toString(36); } exports.expand = function(pathData, shortened) { Funkcja akceptuje poprzednio skrócony adres URL i zwraca adres URL w pełnej postaci. exports.initPathData(pathData); var pathIndex = parseInt(shortened, 36); return pathData.map[pathIndex]; }

Teraz trzeba poinformować Git, że chcemy umieścić nowy plik w repozytorium. Polecenie add w systemie Git działa inaczej niż w pozostałych systemach kontroli wersji. Zamiast dodawać pliki do repozytorium, polecenie dodaje je do tak zwanego staging area w Git. Wymieniony obszar można uznawać za listę wskazującą na pliki nowo dodane, zmodyfikowane lub te, które mają zostać umieszczone w kolejnej wersji repozytorium: git add index.js

System Git teraz „wie”, że powinien monitorować wskazany plik. Jeśli chcesz, do staging area możesz dodać jeszcze inne pliki, ale na obecnym etapie wystarczy tylko jeden wymieniony. Aby nakazać systemowi Git utworzenie nowej wersji repozytorium zawierającego zmodyfikowane pliki umieszczone w staging area, należy wydać polecenie commit. W przypadku innych VCS polecenie commit może akceptować opcję -m pozwalającą na dodanie opisu zmian wprowadzonych w nowej wersji repozytorium: git commit -m "Dodano funkcję skracania adresów URL."

Repozytorium znajdujące się w komputerze lokalnym zawiera teraz nową wersję repozytorium. Wyświetlenie listy zmian repozytorium następuje po wydaniu poniższego polecenia: git log

Przekazanie repozytorium Git do serwisu GitHub Jeżeli na tym etapie komputer lokalny zostanie zniszczony, wówczas stracisz całą dotychczas wykonaną pracę. W celu zabezpieczenia się przed nieprzewidywalnymi kataklizmami i wykorzystania pełni możliwości interfejsu sieciowego GitHub zmiany wprowadzone w lokalnym repozytorium Git należy przekazać do serwisu GitHub. Wcześniej trzeba jednak poinformować Git, gdzie ma być przekazywany kod. To wymaga utworzenia zdalnego repozytorium Git. Tego rodzaju repozytoria są określane mianem zdalnych. Poniższe polecenie pokazuje, jak dodać zdalne repozytorium GitHub. Podaj odpowiednią nazwę użytkownika i zwróć uwagę, że node-elf.git wskazuje nazwę projektu: git remote add origin [email protected]:nazwa-użytkownika/node-elf.git

Po dodaniu zdalnego repozytorium zmiany wprowadzone w kodzie można wysłać do serwisu GitHub. W terminologii Git przekazywanie zmian do repozytorium nosi nazwę push. W poniższym poleceniu nakazujesz Git przekazanie zmian do zdalnego repozytorium origin zdefiniowanego w poprzednim poleceniu. Każde repozytorium Git może mieć jedno lub więcej odgałęzień, które pod względem koncepcji są oddzielnymi obszarami roboczymi w repozytorium. Zmiany wprowadzone w kodzie chcesz przekazać do gałęzi master:

git push -u origin master

W poleceniu push opcja -u informuje Git, że wskazane zdalne repozytorium to upstream i gałąź. Zdalne repozytorium upstream jest domyślnie używanym zdalnym repozytorium. Po pierwszym przekazaniu zmian za pomocą opcji -u kolejne operacje przekazania kodu możesz przeprowadzać za pomocą poniższego polecenia, które jest prostsze i tym samym łatwiejsze do zapamiętania: git push

Jeżeli przejdziesz do serwisu GitHub i odświeżysz stronę repozytorium, zobaczysz nowo dodany plik. Utworzenie i hosting modułu w GitHub to łatwy sposób na umożliwienie wielokrotnego wykorzystania danego modułu. Na przykład jeżeli będziesz chciał go użyć w innym projekcie, wówczas wystarczy wydać przedstawione poniżej polecenia: mkdir ~/tmp/my_project/node_modules cd ~/tmp/my_project/node_modules git clone https://github.com/mcantelon/node-elf.git elf cd ..

Polecenie require('elf') w kodzie projektu zapewnia później dostęp do modułu. Zwróć uwagę, że podczas klonowania repozytorium używasz ostatniego argumentu powłoki w celu nadania nazwy katalogowi, w którym ma zostać umieszczony sklonowany kod. Teraz już wiesz, jak dodawać projekty do serwisu GitHub, jak utworzyć repozytorium w GitHub, jak utworzyć i dodać pliki do repozytorium Git w komputerze lokalnym oraz jak przekazać wprowadzone zmiany do zdalnego repozytorium. W internecie znajdziesz zasoby, dzięki którym będziesz potrafił wykorzystać system kontroli wersji w jeszcze większym stopniu. Jeżeli szukasz dokładnych informacji z zakresu użycia systemu Git, przeczytaj książkę „Pro Git” jednego z założycieli serwisu GitHub, czyli Scotta Chacona. Można ją kupić lub przeczytać bezpłatnie w internecie (http://git-scm.com/book/pl/). Jeżeli wolisz podejście bardziej praktyczne, zapoznaj się z dokumentacją dostępną w oficjalnej witrynie systemu Git (http://git-scm.com/documentation), gdzie wymieniono listę wielu samouczków, dzięki którym rozpoczniesz pracę z Git.

14.2.3. Współpraca przez serwis GitHub Skoro już wiesz, jak zupełnie od początku utworzyć repozytorium GitHub, zobacz, jak można je wykorzystać do współpracy z innymi. Przyjmujemy założenie, że używasz modułu opracowanego przez firmę trzecią i

natknąłeś się na błąd. Możesz przejrzeć kod źródłowy modułu i spróbować znaleźć sposób na usunięcie błędu. Następnie wysyłasz wiadomość e-mail do autora modułu, opisujesz znaleziony błąd i dołączasz pliki zawierające przygotowane poprawki. Takie podejście wymaga od autora wykonania pewnej żmudnej pracy. Autor porównuje otrzymane pliki z najnowszą wersją kodu, a następnie umieszcza w niej poprawki otrzymane od Ciebie. Jeżeli autor korzysta z serwisu GitHub, wówczas możesz sklonować repozytorium projektu autora, wprowadzić pewne zmiany, a następnie przez GitHub poinformować autora o wprowadzeniu poprawki usuwającej znaleziony błąd. GitHub pozwoli autorowi na wyświetlenie różnic między oryginalnym kodem i przygotowanym przez Ciebie. Jeżeli poprawka zostanie zaakceptowana, przygotowane przez Ciebie zmiany zostaną za pomocą pojedynczego kliknięcia myszą umieszczone w najnowszej wersji kodu projektu przygotowanego przez autora modułu. W żargonie GitHub duplikowanie repozytorium nosi nazwę rozwidlenia (ang. forking). Rozwidlenie projektu pozwala na wprowadzenie dowolnych zmian we własnej kopii projektu bez obaw o uszkodzenie oryginalnego repozytorium. Nie musisz mieć zgody autora repozytorium na utworzenie rozwidlenia: każdy może utworzyć rozwidlenie projektu i wprowadzone przez siebie zmiany przekazać autorowi. Autor wcale nie musi zaakceptować otrzymanych zmian, ale nawet wówczas zachowujesz własną wersję z wprowadzonymi zmianami, którą możesz samodzielnie obsługiwać i rozwijać. Jeżeli rozwidlona wersja projektu zyska popularność, inni mogą tworzyć jego rozwidlenia i Tobie oferować wprowadzane przez siebie usprawnienia. Po wprowadzeniu zmian w rozwidleniu projektu możesz je przekazać autorowi oryginalnego projektu poprzez tak zwane żądanie przekazania zmian (ang. pull request), które jest wiadomością skierowaną do autora repozytorium i zawierającą propozycję wprowadzenia zmian. W żargonie GitHub pull oznacza import zmian z rozwidlenia i połączenie ich z kodem w innym rozwidleniu. Na rysunku 14.5 pokazano przykład typowej współpracy za pomocą GitHub. Przeanalizujemy teraz przykład rozwidlenia repozytorium GitHub na potrzeby współpracy nad projektem. Proces został pokazany na rysunku 14.6. Rozwidlenie rozpoczyna proces współpracy przed powieleniem repozytorium GitHub na własnym koncie (co nosi nazwę rozwidlenia) — patrz krok A. Następnie rozwidlone repozytorium zostaje sklonowane do komputera lokalnego (B), w którym po wprowadzeniu zmian są one zatwierdzane (C). Kolejnym krokiem jest przekazanie wprowadzonych zmian z powrotem do serwisu GitHub (D) i wysłanie żądania przekazania zmian autorowi oryginalnego repozytorium z prośbą o rozważenie ich uwzględnienia (E). Jeżeli autor będzie chciał umieścić zmiany w oryginalnym repozytorium, wtedy zaakceptuje wspomniane żądanie przekazania zmian.

Rysunek 14.5. Typowa współpraca przez serwis GitHub

Rysunek 14.6. Proces współpracy nad repozytorium GitHub przez utworzenie rozwidlenia

Przyjmujemy założenie, że chcesz utworzyć rozwidlenie repozytorium node-elf przygotowanego wcześniej w rozdziale, a następnie dodać kod odpowiedzialny za eksport wersji modułu. W ten sposób użytkownicy modułu zyskają pewność, że używają jego odpowiedniej wersji. Przede wszystkim zaloguj się w serwisie GitHub i przejdź na stronę główną r epoz ytor ium https://github.com/mcantelon/node-elf. Znajdziesz na niej

przycisk Fork duplikujący repozytorium. Strona wynikowa będzie podobna do strony oryginalnego repozytorium, ale z komunikatem w stylu forked from mcantelon/node-elf wyświetlanym pod nazwą repozytorium. Po utworzeniu rozwidlenia kolejne kroki to utworzenie kopii repozytorium w komputerze lokalnym, wprowadzenie zmian, a następnie przekazanie ich z powrotem do GitHub. Poniższe polecenia pokazują, jak to zrobić w przypadku repozytorium node-elf: mkdir -p ~/tmp/forktest cd ~/tmp/forktest git clone [email protected]:chickentown/node-elf.git cd node-elf echo "exports.version = '0.0.2';" >> index.js git add index.js git commit -m "Dodano specyfikację wersji modułu." git push origin master

Po wprowadzeniu zmian kliknij przycisk Pull Request na stronie rozwidlenia repozytorium, podaj temat i treść wiadomości, opisując dokonane zmiany. Następnie kliknij przycisk Send Pull Request. Na rysunku 14.7 pokazano przykład typowej treści wiadomości.

Rysunek 14.7. Szczegóły dotyczące żądania przekazania zmian do repozytorium GitHub

Żądanie przekazania kodu zostanie umieszczone na stronie problemów oryginalnego repozytorium. Opiekun oryginalnego repozytorium może po przejrzeniu proponowanych zmian wprowadzić je w repozytorium, klikając przycisk Merge Pull Request, a następnie podać informacje związane z zatwierdzanymi zmianami i kliknąć Confirm Merge. W ten sposób zgłoszenie będzie automatycznie zamknięte. Po współpracy z innymi i utworzeniu wspaniałego modułu kolejnym krokiem jest udostępnienie go społeczności. Najlepszym rozwiązaniem jest wtedy dodanie modułu do repozytorium npm.

14.3. Przekazanie własnego modułu do repozytorium npm

Przypuśćmy, że od pewnego czasu pracowałeś nad modułem przeznaczonym do skracania adresów URL. Teraz uważasz, że moduł w obecnej wersji może być użyteczny dla innych programistów Node. Aby go opublikować, na grupie Google związanej z Node możesz umieścić opis funkcjonalności modułu. W ten sposób jednak ograniczysz liczbę osób, do których dotrzesz. Ponadto nie będziesz miał łatwego sposobu na poinformowanie osób, które zaczną korzystać z Twojego modułu, o wydaniu jego nowszej wersji. Rozwiązaniem problemów związanych z udostępnianiem modułu i jego uaktualnianiem jest publikacja w repozytorium npm. Za pomocą repozytorium npm można bardzo łatwo zdefiniować zależności projektu i pozwolić na ich automatyczną instalację wraz z modułem. Jeżeli utworzyłeś moduł przeznaczony do przechowywania komentarzy dotyczących treści (na przykład postów bloga), możesz wówczas dołączyć jako zależność moduł obsługujący bazę danych MongoDB przeznaczoną na komentarze. W przypadku modułu dostarczającego narzędzie działające z poziomu powłoki zależnością może być moduł pomocniczy przeznaczony do przetwarzania argumentów powłoki. Jak dotąd w książce używaliśmy repozytorium npm do instalacji wszystkiego, począwszy od testowanych frameworków, aż po sterowniki baz danych. Jednak niczego jeszcze nie opublikowaliśmy. W kolejnych punktach przeczytasz, jak wykonać wymienione poniżej kroki w celu publikacji własnej pracy w repozytorium npm. 1. Przygotowanie pakietu. 2. Przygotowanie specyfikacji pakietu. 3. Testowanie pakietu. 4. Publikacja pakietu. Zaczynamy od przygotowania pakietu.

14.3.1. Przygotowanie pakietu Każdy moduł Node, który masz zamiar udostępnić światu, powinien składać się z odpowiednich zasobów, takich jak dokumentacja, przykłady, testy i powiązane z modułem narzędzia działające w powłoce. Ponadto moduł powinien zawierać p l i k README wraz z wystarczającą ilością informacji, aby można było umożliwić użytkownikom szybkie rozpoczęcie pracy z modułem. Katalog modułu powinien być zorganizowany za pomocą podkatalogów. W tabeli 14.2 przedstawiono zgodne z konwencją podkatalogi projektu Node — bin, docs, example, lib i test — oraz ich przeznaczenie. Tabela 14.2. Zgodne z konwencją podkatalogi projektu Node Katalog

Opis

bin

Skrypty powłoki.

docs

Dokumentacja.

example

Przykład użycia aplikacji.

lib

Podstawowa funkcjonalność aplikacji.

test

Skrypty testowe i powiązane z tym zasoby.

Po przygotowaniu odpowiedniej struktury dla pakietu należy go przygotować do publikacji w repozytorium npm poprzez utworzenie specyfikacji pakietu.

14.3.2. Przygotowanie specyfikacji pakietu Kiedy publikujesz pakiet w repozytorium npm, konieczne jest dołączenie czytelnego dla komputera pliku specyfikacji pakietu. Wspomniany plik w formacie JSON nosi nazwę package.json i zawiera informacje o module, takie jak nazwa, opis, wersja, zależności, a także inne cechy charakterystyczne. Nodejitsu oferuje użyteczną stronę internetową (http://package.json.nodejitsu.com/) pokazującą przykładowy plik package.json i objaśniającą poszczególne fragmenty pliku po umieszczeniu nad nimi kursora myszy. W pliku package.json tylko nazwa i wersja to dane obowiązkowe. Pozostałe informacje są opcjonalne, ale jeśli niektóre z nich zostaną podane, to znacznie zwiększą użyteczność modułu. Dzięki zdefiniowaniu charakterystyki bin menedżer npm będzie „wiedział”, które pliki pakietu są narzędziami powłoki, i globalnie je udostępni. Przykładowa specyfikacja pakietu może przedstawiać się następująco: { "name": "elf" , "version": "0.0.1" , "description": "Toy URL shortener" , "author": "Mike Cantelon " , "main": "index" , "engines": { "node": "0.4.x" } }

Więcej dokładnych informacji o dostępnych otrzymasz po wydaniu poniższego polecenia:

opcjach

pliku package.json

npm help json

Ponieważ ręczne wygenerowanie pliku JSON jest tylko nieco bardziej zabawne od ręcznego tworzenia pliku XML, zapoznamy się z pewnymi narzędziami

ułatwiającymi to zadanie. Jednym z nich jest ngen, czyli dostępny w repozytorium npm pakiet, który dodaje polecenie powłoki o nazwie ngen. Po zadaniu kilku pytań narzędzie wygeneruje plik package.json. Ponadto generuje ono także wiele innych plików, które standardowo są umieszczane w pakietach npm, na przykład Readme.md. Instalacja narzędzia ngen następuje po wydaniu poniższego polecenia: npm install -g ngen

Po zainstalowaniu narzędzia ngen uzyskujesz globalne polecenie ngen, które wywołane w katalogu głównym projektu wyświetli kilka pytań, a następnie wygeneruje plik package.json oraz pozostałe, najczęściej stosowane w pakietach Node. Niepotrzebnie wygenerowane pliki można usunąć. Wygenerowany zostaje między innymi plik .gitignore wskazujący pliki i katalogi, które nie powinny być dodawane do repozytorium Git projektu podczas jego publikacji w npm. Ponadto generowany jest plik .npmignore pełniący podobną rolę jak .gitignore i wskazujący pliki, które mają być zignorowane podczas publikacji pakietu w npm. Poniżej przedstawiono przykładowe dane wyjściowe po wydaniu polecenia

ngen:

Project name: elf Enter your name: Mike Cantelon Enter your email: [email protected] Project description: URL shortening library create : /Users/mike/programming/js/shorten/node_modules/.gitignore create : /Users/mike/programming/js/shorten/node_modules/.npmignore create : /Users/mike/programming/js/shorten/node_modules/History.md create : /Users/mike/programming/js/shorten/node_modules/index.js ...

Wygenerowanie pliku package.json to najtrudniejsze zadanie podczas publikacji modułu w repozytorium npm. Po zakończeniu tego kroku jesteś gotowy do opublikowania modułu.

14.3.3. Testowanie i publikowanie pakietu Opublikowanie modułu w repozytorium npm obejmuje trzy wymienione poniżej kroki, które zostaną omówione w tym punkcie: 1. Przetestowanie instalacji lokalnej pakietu. 2. Dodanie użytkownika

npm,

o ile jeszcze tego nie zrobiłeś.

3. Publikacja modułu w repozytorium npm.

Testowanie instalacji pakietu Aby przetestować lokalną instalację pakietu, należy użyć polecenia

link

menedżera npm i wydać je z poziomu katalogu głównego modułu. To polecenie spowoduje globalne udostępnienie modułu w komputerze i Node będzie mogło z niego korzystać tak jak w przypadku pakietu konwencjonalnie zainstalowanego przez npm. sudo npm link

Po globalnym udostępnieniu pakietu można go zainstalować w oddzielnym katalogu testowym, używając poniższego polecenia link wraz z nazwą pakietu: npm link elf

Po instalacji pakietu szybki test polega na użyciu funkcji require() w interfejsie REPL Node, jak przedstawiono w poniższym wierszu kodu. Wynikiem są zmienne lub funkcje dostarczane przez moduł: node > require('elf'); { version: '0.0.1', initPathData: [Function], shorten: [Function], expand: [Function] }

Jeżeli pakiet przeszedł test i zakończyłeś już nad nim prace, wtedy z poziomu katalogu głównego modułu należy wydać polecenie unlink menedżera npm: sudo npm unlink

Teraz moduł nie będzie już dłużej dostępny globalnie w komputerze. Po zakończeniu procesu publikacji pakietu w repozytorium npm będziesz mógł go zainstalować w standardowy sposób, czyli przez wydanie polecenia install menedżera npm. Po przetestowaniu pakietu kolejnym krokiem jest utworzenie konta npm przeznaczonego do publikacji pakietów, o ile takiego konta nie utworzyłeś już wcześniej.

Dodanie użytkownika npm Poniższe polecenie powoduje utworzenie Twojego przeznaczonego do publikacji pakietu w repozytorium npm:

własnego

konta

npm adduser

Zostaniesz poproszony o podanie nazwy użytkownika, adresu e-mail i hasła do konta. Jeżeli operacja tworzenia konta zakończy się powodzeniem, na ekranie nie będzie wyświetlony żaden komunikat błędu.

Publikacja w repozytorium npm Kolejnym krokiem jest publikacja modułu. Wydanie poniższego polecenia powoduje opublikowanie modułu:

npm publish

Na ekranie zostanie wyświetlony komunikat Sending authorization over an insecure channel (wysyłanie danych uwierzytelniających przez niezabezpieczony kanał), ale jeśli nie zobaczysz żadnych dodatkowych komunikatów błędu, będzie to oznaczało, że publikacja modułu zakończyła się powodzeniem. Sukces publikacji modułu możesz potwierdzić za pomocą polecenia view menedżera npm: npm view elf description

Jeżeli chcesz, możesz dołączyć jedno lub więcej prywatnych repozytoriów jako zależności pakietu. Tego rodzaju sytuacja występuje, gdy być może masz moduł z użytecznymi funkcjami pomocniczymi, których chcesz używać, ale nie chcesz udostępniać ich publicznie w repozytorium npm. Aby dodać zależność w postaci prywatnego repozytorium, w miejscu przeznaczonym do umieszczania nazw zależności modułu podaj dowolną nazwę inną niż pozostałe zależności. Tam, gdzie normalnie umieszcza się wersję pakietu, podaj adres URL repozytorium Git. W przedstawionym poniżej przykładzie będącym fragmentem pliku package.json ostatnia zależność przedstawia repozytorium prywatne: "dependencies" : { "optimist" : ">=0.1.3", "iniparser" : ">=1.0.1", "mingy": ">=0.1.2", "elf": "git://github.com/mcantelon/node-elf.git" },

Pamiętaj, że wszelkie moduły prywatne również powinny zawierać pliki package.json. Aby upewnić się, że tego rodzaju moduły nie zostaną przypadkowo opublikowane, w pliku package.json przypisz właściwości private wartość true: "private": true,

W ten sposób już wiesz, jak skonfigurować, przetestować i opublikować własny moduł w repozytorium npm.

14.4. Podsumowanie Podobnie jak jest w przypadku większości projektów open source, które osiągnęły sukces, także Node charakteryzuje się aktywną społecznością w internecie. Oznacza to dostępność ogromnej ilości zasobów internetowych, a także możliwość szybkiego znalezienia odpowiedzi na nurtujące Cię pytania. Do wspomnianych zasobów zaliczamy między innymi grupy Google, kanały IRC i strony zgłaszania błędów w GitHub.

Poza miejscem zgłaszania błędów serwis GitHub oferuje również możliwość hostingu repozytorium Git i przeglądania kodu repozytorium Git za pomocą przeglądarki internetowej. Dzięki GitHub inni programiści mogą łatwo tworzyć rozwidlenia Twojego kodu open source, aby wprowadzać w nim poprawki, dodawać nowe funkcje lub w ogóle skierować projekt na nowe tory. Zmiany wprowadzone w kodzie można bardzo łatwo przekazywać z powrotem do oryginalnego repozytorium. Od chwili udostępnienia użytkownikom projektu Node mogą oni przekazywać własne moduły do repozytorium Node Package Manager. Dzięki publikacji modułu w npm inni użytkownicy łatwiej go znajdą. A jeśli Twój projekt jest modułem, umieszczenie go w repozytorium npm znacznie ułatwi instalację modułu. Teraz już wiesz, gdzie szukać pomocy, jak współpracować z innymi przez internet oraz jak dzielić się owocami swojej pracy. Node istnieje dzięki aktywnej i zaangażowanej w prace nad nim społeczności. Zachęcamy Cię, abyś stał się aktywnym członkiem wspomnianej społeczności Node!

Dodatek A Instalacja Node i dodatki opracowane przez społeczność Node można bardzo łatwo zainstalować w większości systemów operacyjnych za pomocą konwencjonalnych aplikacji instalatorów lub też z użyciem wiersza poleceń. Drugie z wymienionych rozwiązań jest bardzo łatwe do wykonania w systemach OS X i Linux, ale niezalecane w Windows. Aby pomóc Ci w rozpoczęciu pracy, w tym dodatku dokładnie omówiono instalację Node w systemach operacyjnych OS X, Windows i Linux. Na końcu dodatku dowiesz się, jak używać menedżera pakietów Node (npm) do wyszukiwania i instalacji użytecznych dodatków.

A.1. Instalacja w systemie OS X Instalacja Node w systemie OS X jest bardzo prosta. Pokazany na rysunku A.1 oficjalny instalator jest dostępny na stronie http://nodejs.org/download/ i pozwala na łatwą instalację skompilowanej wersji Node i menedżera npm. Jeżeli jednak wolisz przeprowadzić instalację ze źródeł, możesz skorzystać z narzędzia o nazwie Homebrew (http://brew.sh/), które automatyzuje proces instalacji ze źródeł. Ewentualnie przeprowadź ręczną instalację ze źródeł. Warto w tym miejscu wspomnieć, że instalacja Node ze źródeł w systemie OS X wymaga posiadania zainstalowanych w systemie narzędzi programistycznych OS X. Xcode. Jeżeli nie masz jeszcze zainstalowanego środowiska programistycznego Xcode, możesz je pobrać ze strony Apple (http://developer.apple.com/downloads/). Uzyskanie dostępu do wymienionej strony wymaga przeprowadzenia bezpłatnej rejestracji konta programisty na platformie Apple. Xcode to całkiem duża aplikacja (około 4 GB). Alternatywnym rozwiązaniem oferowanym przez Apple jest pakiet Command Line Tools for Xcode, który można pobrać z tej samej strony. Wymieniony pakiet zawiera minimalną ilość narzędzi potrzebnych do kompilacji Node oraz innych projektów oprogramowania typu open source.

Rysunek A.1. Oficjalny instalator Node dla systemu OS X

Aby szybko sprawdzić, czy masz zainstalowaną w systemie aplikację Xcode, uruchom narzędzie Terminal, a następnie wydaj polecenie xcodebuild. Jeżeli masz zainstalowaną aplikację Xcode, otrzymasz komunikat błędu informujący, że w katalogu bieżącym nie znaleziono projektu Xcode. Podczas instalacji konieczne może być wydawanie poleceń z poziomu powłoki. Do tego celu wykorzystaj narzędzie Terminal, które standardowo znajduje się w katalogu Programy/Narzędzia. Jeżeli przeprowadzasz kompilację ze źródeł, zajrzyj do podrozdziału A.4, w którym znajdziesz omówienie kroków niezbędnych do wykonania.

A.1.1. Instalacja za pomocą Homebrew W systemie OS X Node można bardzo łatwo zainstalować za pomocą Homebrew, czyli menedżera pakietów przeznaczonego do instalacji oprogramowania typu open source. Pierwszym krokiem jest instalacja samego menedżera Homebrew, co wymaga wydania poniższego polecenia w powłoce: ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"

Gdy menedżer Homebrew znajduje się już w systemie, instalację Node możesz przeprowadzić, wydając polecenie: brew install node

Podczas kompilacji kodu przez Homebrew w oknie narzędzia Terminal będzie wyświetlana duża ilość tekstu, który możesz przewijać. Wspomniany tekst zawiera informacje związane z procesem kompilacji; możesz je spokojnie zignorować.

A.2. Instalacja w systemie Windows W systemie Windows instalację Node można przeprowadzić za pomocą oficjalnego instalatora dostępnego na stronie http://nodejs.org/download/. Po instalacji Node i menedżera npm będziesz mógł uruchamiać z poziomu wiersza poleceń Windows. Rozwiązanie alternatywne polega na instalacji Node przez przeprowadzenie kompilacji ze źródeł. Takie podejście jest znacznie bardziej skomplikowane i wymaga użycia projektu o nazwie Cygwin, który dostarcza środowisko zgodne z systemem UNIX. Niemal na pewno będziesz chciał uniknąć użycia Node za pomocą projektu Cygwin, o ile nie próbujesz używać modułów, które w Windows nie działają w innej konfiguracji lub wymagają kompilacji (przykładem mogą być tutaj moduły sterowników pewnych baz danych). Aby przeprowadzić instalację Cygwin, przejdź na stronę http://cygwin.com/install.html, a następnie pobierz plik setup.exe. Dwukrotne kliknięcie pobranego pliku powoduje uruchomienie kreatora instalacji. Naciskaj przycisk Dalej, zatwierdzając tym samym opcje domyślne, aż do chwili dotarcia do kroku o nazwie Choose a Download Site. Wybierz dowolną witrynę z listy, a następnie kliknij przycisk Dalej. Jeżeli kreator wyświetli komunikat ostrzeżenia, naciśnij przycisk OK, aby kontynuować proces instalacji. Kreator powinien teraz wyświetlić pokazane na rysunku A.2 okno wyboru pakietów Cygwin.

Rysunek A.2. Okno wyboru pakietów Cygwin pozwala na wskazanie oprogramowania open source, które zostanie zainstalowane w systemie

Korzystając z wymienionego okna, wybierz pakiety, które mają znaleźć się w tworzonym środowisku zgodnym z systemem UNIX. Listę pakietów potrzebnych do pracy z Node przedstawiono w tabeli A.1. Tabela A.1. Pakiety Cygwin niezbędne do uruchomienia Node Kategoria

Pakiet

devel

gcc4-g++

devel

git

devel

make

devel

openssl-devel

devel

pkg-config

devel

zlib-devel

net

inetutils

python

python

web

wget

Po wybraniu wymaganych pakietów kliknij przycisk Dalej.

W kolejnym kroku zostanie wyświetlona lista pakietów będących zależnościami dla wybranych wcześniej. Ponieważ je również musisz zainstalować, kliknij pr z ycisk Dalej, aby zaakceptować wskazane pakiety. Cygwin rozpocznie pobieranie potrzebnych pakietów. Kiedy pobieranie zakończy się, naciśnij przycisk Zakończ. Uruchom Cygwin, klikając ikonę umieszczoną na pulpicie lub w menu Start. Na ekranie zostanie wyświetlony wiersz poleceń. Teraz możesz już przystąpić do kompilacji Node (wymagane kroki zostały omówione w podrozdziale A.4).

A.3. Instalacja w systemie Linux Instalacja Node w systemie Linux zwykle jest bardzo prosta. W tym dodatku przedstawiono instalację na podstawie kodu źródłowego w dwóch popularnych dystrybucjach: Ubuntu i CentOS. Node jest dostępne także za pomocą menedżerów pakietów w wielu innych dystrybucjach. Informacje dotyczące instalacji znajdziesz w serwisie GitHub: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager.

A.3.1. Przygotowania do instalacji w Ubuntu Przed rozpoczęciem instalacji Node w Ubuntu konieczna jest instalacja wymaganych pakietów. W systemie Ubuntu 11.04 lub nowszym wystarczy wydać poniższe polecenie: sudo apt-get install build-essential libssl-dev

sudo — polecenie sudo jest używane do wykonania innego polecenia z uprawnieniami superużytkownika (nazywanego również root). Polecenie sudo jest bardzo często stosowane podczas instalacji oprogramowania, ponieważ pliki muszą być umieszczane w chronionych obszarach systemu plików, a użytkownik root ma dostęp do każdego pliku w systemie niezależnie od uprawnień zdefiniowanych dla pliku.

A.3.2. Przygotowania do instalacji w CentOS Przed instalacją Node w CentOS konieczna jest instalacja wymaganych pakietów. W systemie CentOS 5 lub nowszym wystarczy wydać poniższe polecenia: sudo yum groupinstall 'Development Tools' sudo yum install openssl-devel

Po zainstalowaniu wymaganych pakietów można przystąpić do kompilacji Node.

A.4. Kompilacja Node Kroki niezbędne do przeprowadzenia kompilacji Node są takie same we wszystkich systemach operacyjnych. W powłoce należy wydać poniższe polecenie powodujące utworzenie katalogu tymczasowego, do którego pobierzemy kod źródłowy Node: mkdir tmp

Kolejnym krokiem jest przejście do utworzonego wcześniej katalogu: cd tmp

Teraz wydaj poniższe polecenie, aby pobrać kod źródłowy Node: curl -O http://nodejs.org/dist/node-latest.tar.gz

Na ekranie będą wyświetlane informacje o postępie podczas pobierania pliku. Po pobraniu pliku w powłoce zostanie wyświetlony znak zachęty. Wydanie poniższego polecenia powoduje rozpakowanie pobranego archiwum: tar zxvf node-latest.tar.gz

W trakcie rozpakowywania na ekranie będzie wyświetlona duża ilość danych wyjściowych operacji, a następnie ponownie zobaczysz znak zachęty. Poniższe polecenie powoduje wyświetlenie listy plików znajdujących się w katalogu bieżącym, między innymi nazwy rozpakowanego przed chwilą katalogu: ls

Wydanie poniższego polecenia powoduje przejście do wspomnianego katalogu: cd node-v*

W tym momencie znajdujesz się w katalogu zawierającym kod źródłowy Node. Wydanie poniższego polecenia powoduje uruchomienie skryptu konfiguracyjnego, który przygotowuje instalację oprogramowania dla używanego przez Ciebie systemu operacyjnego: ./configure

Kompilacja Node jest prosta i wymaga wydania poniższego polecenia: make

Proces kompilacji może wymagać nieco czasu, a więc zachowaj cierpliwość. W trakcie kompilacji na ekranie będzie wyświetlana ogromna ilość komunikatów. Wspomniane komunikaty zawierają informacje związane z procesem kompilacji i możesz je spokojnie zignorować. Niedoskonałość Cygwin. Jeżeli uruchomiłeś Cygwin w systemie Windows 7 lub Vista, w trakcie kompilacji mogą wystąpić błędy. Wynikają one z problemów związanych z Cygwin, a nie Node. Rozwiązaniem jest zamknięcie wiersza poleceń Cygwin, a następnie uruchomienie aplikacji wiersza poleceń

ash.exe (znajdziesz ją w katalogu Cygwin, najczęściej c:\cygwin\bin\ash.exe). W uruchomionym wierszu poleceń wydaj polecenie /bin/rebaseall -v. Po wykonaniu polecenia uruchom ponownie komputer. To powinno rozwiązać problemy związane z Cygwin. Na tym etapie już prawie zakończyliśmy instalację. Kiedy przestaną pojawiać się nowe komunikaty i ponownie zobaczysz znak zachęty, wydaj ostatnie polecenie w procesie instalacji Node: sudo make install

Po wykonaniu powyższego polecenia wydanie poniższego spowoduje wyświetlenie numeru wersji Node. Dzięki temu można potwierdzić, że instalacja zakończyła się powodzeniem. node -v

W ten sposób zainstalowałeś Node w systemie!

A.5. Używanie menedżera pakietów Node Po zainstalowaniu Node zyskasz możliwość użycia wbudowanych modułów zapewniających dostęp do API przeznaczonego do wykonywania zadań związanych z siecią, pracy z systemem plików oraz innych operacji najczęściej wymaganych w aplikacjach. Wbudowane moduły Node są określane mianem podstawowych modułów Node. Wprawdzie oferują one wiele użytecznych funkcji, ale prawdopodobnie będziesz chciał korzystać także z funkcji opracowanych przez społeczność. Na rysunku A.3 pokazano koncepcję relacji między modułami podstawowymi Node i dodatkowymi. W zależności od używanego języka programowania idea opracowywanych przez społeczność repozytoriów zawierających moduły z funkcjami dodatkowymi może być Ci znana lub nieznana. Wspomniane repozytoria przypominają biblioteki i stanowią użyteczne bloki budulcowe aplikacji pomagające w realizacji zadań, których nie można w łatwy sposób wykonać za pomocą standardowego języka. Repozytoria są najczęściej modułowe: zamiast od razu pobierać całą bibliotekę, wystarczy pobrać jedynie niezbędne dodatki. Społeczność Node opracowała własne narzędzie przeznaczone do zarządzania dodatkami: menedżer npm (ang. Node Package Manager). W tym podrozdziale dowiesz się, jak używać menedżera npm do wyszukiwania dodatków, przeglądania dokumentacji dodatków, a także analizy kodu źródłowego dodatków.

Rysunek A.3. Stos Node składa się z funkcji dostępnych globalnie, modułów podstawowych oraz opracowanych przez społeczność

W moim systemie nie ma menedżera npm Jeżeli zainstalowałeś Node, to menedżer npm również został zainstalowany. Możesz się o tym przekonać, wydając polecenie npm w powłoce i sprawdzając, jaka będzie odpowiedź powłoki. Jeśli faktycznie nie masz menedżera npm w systemie, jego instalacja wymaga wydania poniższych poleceń: cd /tmp git clone git://github.com/isaacs/npm.git cd npm sudo make install

Po zainstalowaniu menedżera npm wydaj poniższe polecenie, aby upewnić się, że działa (polecenie nakazuje wyświetlenie numeru wersji menedżera): npm -v

Jeżeli menedżer jest zainstalowany prawidłowo, powinieneś zobaczyć dane

wyjściowe (numer wersji) podobne do poniższych: 1.0.3

W przypadku jakichkolwiek problemów z instalacją npm najlepszym rozwiązaniem jest przejście na stronę projektu npm w serwisie GitHub (https://github.com/npm/npm), na której znajdziesz aktualne informacje dotyczące instalacji menedżera.

A.5.1. Wyszukiwanie pakietów Menedżer npm oferuje wygodne rozwiązanie w zakresie uzyskiwania dostępu do dodatków Node opracowanych przez społeczność. Wspomniane dodatki są nazywane pakietami i są przechowywane w repozytorium. Dla użytkowników PHP, Ruby i Perla menedżer npm można określić jako odpowiedniki narzędzi PEAR, Gem i CPAN. Menedżer npm jest niezwykle wygodnym narzędziem. Za jego pomocą można pobierać i instalować pakiety, wydając tylko jedno polecenie. Bardzo łatwo można również wyszukiwać pakiety, przeglądać ich dokumentację, zapoznawać się z kodem źródłowym pakietu, a nawet publikować własne, udostępniając je tym samym całej społeczności Node. Do wyszukiwania pakietów znajdujących się w repozytorium służy polecenie search. Na przykład jeśli chcesz znaleźć generator XML, wtedy możesz wydać poniższe, proste polecenie: npm search xml generator

Pierwsze użycie menedżera npm do operacji wyszukiwania będzie trwało nieco dłużej, ponieważ konieczne jest pobranie informacji o repozytorium. Jednak kolejne operacje wyszukiwania będą przeprowadzane już znacznie szybciej. Alternatywnym rozwiązaniem dla polecenia wydawanego w powłoce jest interfejs wyszukiwania dostępny przez przeglądarkę internetową. Wystarczy przejść na witrynę https://www.npmjs.org/. Podana witryna (pokazana na rysunku A.4) dostarcza również dane statystyczne dotyczące pakietów, między innymi całkowitą liczbę pakietów, pakiety będące najczęściej zależnościami dla innych pakietów, a także ostatnio uaktualnione.

Rysunek A.4. Witryna npmjs.org podaje użyteczne dane statystyczne dotyczące pakietów

Interfejs w postaci wymienionej witryny internetowej pozwala także na przeglądanie poszczególnych pakietów, wyświetla użyteczne informacje o nich, takie jak zależności, oraz podaje położenie repozytorium kontroli wersji dla pakietu.

A.5.2. Instalacja pakietu Po znalezieniu interesującego Cię pakietu istnieją dwa podstawowe sposoby jego instalacji: lokalnie lub globalnie. Instalacja lokalna powoduje umieszczenie pobranego modułu w katalogu o nazwie node_modules, znajdującym się w bieżącym katalogu roboczym. Jeżeli wymieniony katalog nie istnieje, wtedy menedżer npm utworzy go. Poniżej przedstawiono przykład instalacji lokalnej pakietu o nazwie

express:

npm install express

W systemach operacyjnych innych niż Windows instalacja globalna powoduje umieszczenie pobranego modułu w katalogu o nazwie /usr/local. Wymieniony katalog jest tradycyjnie używany przez systemy z rodziny UNIX do przechowywania aplikacji instalowanych przez użytkownika. Z kolei w systemach Windows dla modułów instalowanych globalnie jest przeznaczony katalog Appdata\Roaming\npm. Poniżej przedstawiono przykład instalacji globalnej pakietu o nazwie

express:

npm install -g express

Jeżeli nie masz wystarczających uprawnień do przeprowadzenia instalacji globalnej, to na początku polecenia powinieneś umieścić sudo, na przykład: sudo npm install -g express

Po zainstalowaniu pakietu kolejnym krokiem jest ustalenie sposobu jego działania. Na szczęście dzięki menedżerowi npm jest to łatwe zadanie.

A.5.3. Przeglądanie dokumentacji i kodu pakietu Menedżer npm oferuje wygodny sposób przeglądania dokumentacji pakietu, o ile taka jest dostępna. Polecenie docs powoduje otworzenie przeglądarki internetowej, w której zostaje wyświetlona dokumentacja wskazanego pakietu. Poniżej przedstawiono przykład wyświetlania dokumentacji pakietu o nazwie express: npm docs express

Dokumentację można wyświetlić, nawet jeśli pakiet nie został zainstalowany. Jeżeli dokumentacja pakietu jest niekompletna lub niejasna, wtedy często użytecznym rozwiązaniem jest przejrzenie jego kodu źródłowego. Menedżer npm ułatwia także i to zadanie. Powoduje utworzenie podpowłoki, w której katalogiem najwyższego poziomu jest bieżący katalog roboczy plików kodu źródłowego pakietu. Poniżej przedstawiono przykład przeglądania plików kodu źródłowego lokalnie zainstalowanego pakietu express: npm explore express

Aby przeglądać kod źródłowy pakietu zainstalowanego globalnie, wystarczy po prostu dodać opcję -g po poleceniu npm, na przykład: npm -g explore express

Analiza kodu źródłowego pakietu do również doskonały sposób nauki. Dzięki przeglądaniu kodu źródłowego Node bardzo często poznajesz nowe techniki programowania oraz organizacji kodu.

Dodatek B Debugowanie Node Podczas prac programistycznych, a zwłaszcza w trakcie poznawania nowego języka lub frameworka, narzędzia i techniki debugowania mogą być niezwykle użyteczne. W tym dodatku poznasz sposoby ustalenia, co tak naprawdę dzieje się w aplikacji Node.

B.1. Analiza kodu za pomocą JSHint Błędy związane ze składnią lub zasięgiem są poważnym problemem w trakcie programowania. Podczas próby określenia podstawowej przyczyny problemu pierwszą linią obrony jest przejrzenie kodu. Jeśli jednak przejrzysz kod źródłowy i od razu nie wychwycisz błędu, kolejną możliwością wartą wypróbowania jest uruchomienie narzędzia sprawdzającego kod źródłowy pod kątem błędów. JSHint to jedno z tego rodzaju narzędzi. Potrafi poinformować o błędach zarówno poważnych, takich jak wywołania funkcji niezdefiniowanych w kodzie, jak i bardziej błahych, na przykład niestosowanie się do konwencji JavaScript w zakresie wielkości znaków w konstruktorach klas. Nawet jeśli nigdy nie korzystałeś z narzędzia JSHint, przeczytanie o wyszukiwanych przez nie błędach pozwala na zdobycie cennej wiedzy o czyhających pułapkach. JSHint to projekt oparty na JSLint, czyli dostępnym od dekady kodzie źródłowym JavaScript narzędzia analizy. Jednak w przeciwieństwie do JSHint wspomniane JSLint to narzędzie nie oferujące zbyt dużych możliwości w zakresie konfiguracji. W opinii wielu osób narzędzie JSLint jest zbyt rygorystyczne pod względem stylistyki. Z kolei JSHint pozwala na wskazanie, co chcesz sprawdzić, a co ma zostać zignorowane. Na przykład średniki są z technicznego punktu widzenia wymagane przez interpretery JavaScript, ale większość interpreterów stosuje zautomatyzowane wstawianie średników (ang. Automated Semicolon Insertion, ASI) i umieszcza je tam, gdzie ich zabrakło. Z tego powodu niektórzy programiści celowo pomijają je w kodzie źródłowym, aby zwiększyć jego przejrzystość, a sam kod działa bez zastrzeżeń. Narzędzie JSLint będzie uznawało brak średników za błąd, natomiast JSHint można skonfigurować w taki sposób, aby ignorowało ten „błąd” i sprawdzało kod pod kątem innych poważnych błędów. Instalacja JSHint udostępnia polecenie powłoki o nazwie jshint, które sprawdza kod źródłowy. Narzędzie JSHint powinno być zainstalowane globalnie za pomocą menedżera npm przez wydanie poniższego polecenia:

npm install -g jshint

Po instalacji JHint można sprawdzić pliki JavaScript przez wydanie polecenia podobnego do poniższego: jshint aplikacja.js

W większości przypadków będziesz chciał utworzyć plik konfiguracyjny dla JSHint wskazujący to, co powinno być sprawdzone. Jednym z możliwych rozwiązań jest skopiowanie do komputera lokalnego domyślnego pliku konfiguracyjnego dostępnego w serwisie GitHub (https://github.com/jshint/node-jshint/blob/master/.jshintrc), a następnie jego modyfikacja. Jeżeli przygotowany plik konfiguracyjny nazwiesz .jshintrc i umieścisz w katalogu aplikacji lub w dowolnym katalogu nadrzędnym dla aplikacji, to narzędzie automatycznie znajdzie ten plik i go użyje. Alternatywne rozwiązanie polega na użyciu opcji config i wskazaniu położenia pliku konfiguracyjnego. Poniższy przykład wywołania JSHint nakazuje narzędziu użycie pliku konfiguracyjnego o niestandardowej nazwie: jshint aplikacja.js --config /home/michal/jshint.json

Więcej informacji na temat konkretnych opcji konfiguracyjnych znajdziesz na stronie http://www.jshint.com/docs/#options.

B.2. Dane wyjściowe debugowania Gdy kod źródłowy wydaje się prawidłowy, ale aplikacja nadal działa w sposób inny od oczekiwanego, wówczas warto wyświetlić dane wyjściowe procesu debugowania, aby jeszcze dokładniej dowiedzieć się, co tak naprawdę dzieje się z aplikacją.

B.2.1. Debugowanie za pomocą modułu console Node zawiera wbudowany moduł console, który oferuje funkcje użyteczne podczas debugowania i wyświetlania danych wyjściowych w konsoli.

Wyświetlanie informacji o stanie aplikacji Funkcja console.log() jest używana do wyświetlania w standardowym wyjściu danych wyjściowych zawierających informacje o stanie aplikacji. Inna nazwa tej funkcji to console.info(). Istnieje możliwość podania funkcji argumentów w stylu printf() (http://pl.wikipedia.org/wiki/Printf): console.log('Counter: %d', counter);

Podobnie działają funkcje

console.warn()

i console.error(), które są przeznaczone

do wyświetlania ostrzeżeń i błędów. Jedyna różnica polega na tym, że dane są kierowane do standardowego wyjścia błędów zamiast do standardowego wyjścia. Dzięki temu można je przekierować (jeśli występuje taka potrzeba) do pliku dziennika zdarzeń, jak przedstawiono w poniższym przykładzie: node server.js 2> error.log

F unk c j a console.dir() wyświetla zawartość wskazanego pokazano przykładowe dane wyjściowe wymienionej funkcji:

obiektu.

Poniżej

{ name: 'Jan Kowalski', interests: [ 'sport', 'polityka', 'muzyka', 'teatr' ] }

Dane wyjściowe dotyczące pomiaru czasu Moduł console zawiera dwie funkcje, które gdy są używane razem, pozwalają na pomiar czasu wykonywania fragmentów kodu. Jednocześnie można mierzyć więcej niż tylko jeden aspekt. Aby rozpocząć pomiar czasu, poniższy wiersz kodu należy umieścić w miejscu, w którym ma się rozpocząć pomiar: console.time('danyKomponent');

W celu zakończenia pomiaru i podania czasu, jaki upłynął od rozpoczęcia pomiaru, w miejscu zakończenia pomiaru trzeba umieścić wiersz kodu: console.timeEnd('danyKomponent');

Powyższy kod wyświetli zmierzony czas.

Wyświetlanie stosu wywołań Stos wywołań dostarcza informacje o funkcjach wywoływanych przed dotarciem do wskazanego punktu w logice aplikacji. Kiedy w trakcie działania programu Node wystąpi błąd, wówczas może zostać wyświetlony stos wywołań zawierający informacje o tym, co w logice aplikacji doprowadziło do wystąpienia błędu. W dowolnym punkcie aplikacji można wyświetlić stos wywołań bez konieczności zatrzymywania działania aplikacji. W tym celu wystarczy jedynie wywołać funkcję console.trace(). Wspomniane poniższych:

wywołanie

spowoduje

wyświetlenie

Trace: at lastFunction (/Users/mike/tmp/app.js:12:11) at secondFunction (/Users/mike/tmp/app.js:8:3) at firstFunction (/Users/mike/tmp/app.js:4:3) at Object. (/Users/mike/tmp/app.js:15:3) ...

danych

podobnych

do

Pamiętaj, że informacje znajdujące się na stosie wywołań są wyświetlane w odwrotnej kolejności chronologicznej.

B.2.2. Użycie modułu debug do zarządzania danymi wyjściowymi procesu debugowania Dane wyjściowe debugowania są użyteczne, ale jeśli akurat nie będziesz aktywnie szukał problemu, wtedy mogą wprowadzać jedynie dodatkowe zamieszanie. Idealnym rozwiązaniem jest możliwość włączania i wyłączania wyświetlania danych wyjściowych debugowania. Jednym ze sposobów włączania i wyłączania danych wyjściowych debugowania jest użycie zmiennej środowiskowej. Opracowany przez T.J. Holowaychuka moduł debug oferuje użyteczne narzędzie do tego celu. Zarządzanie wyświetlaniem danych wyjściowych debugowania następuje za pomocą zmiennej środowiskowej DEBUG. Więcej informacji na ten temat znajdziesz w rozdziale 13.

B.3. Debuger wbudowany w Node Gdy potrzeby w zakresie debugowania wykraczają poza proste dane wyjściowe procesu debugowania, Node oferuje wbudowany debuger działający z poziomu powłoki. Wywołanie debugera następuje przez uruchomienie aplikacji wraz ze słowem kluczowym debug, na przykład: node debug server.js

Po uruchomieniu aplikacji w ten sposób na ekranie zobaczysz kilka pierwszych wierszy aplikacji oraz znak zachęty debugera, jak pokazano na rysunku B.1.

Rysunek B.1. Uruchomienie debugera wbudowanego w Node

Komunikat break in server.js:1 oznacza, że debuger zatrzymał wykonywanie programu przed wykonaniem pierwszego wiesza kodu.

B.3.1. Nawigacja po debugerze Po wyświetleniu znaku zachęty debugera można kontrolować działanie aplikacji. Polecenie next (lub po prostu n) powoduje wykonanie kolejnego wiesza kodu. Z kolei polecenie cont (lub c) powoduje wykonywanie aplikacji, dopóki nie zostanie przerwane. Zatrzymanie debugera może nastąpić przez zakończenie działania aplikacji lub po dojściu do tak zwanego punktu kontrolnego. Wspomniane punkty kontrolne to punkty, w których debuger ma wstrzymać działanie aplikacji, aby można było przeanalizować jej stan. Jednym ze sposobów dodania punktu kontrolnego jest dodanie w aplikacji wiersza, w którym ma zostać umieszczony punkt kontrolny. Wspomniany wiersz powinien zawierać polecenie debugger;, jak przedstawiono w listingu B.1. Wiersz debugger; nie ma żadnego negatywnego wpływu na normalne działanie aplikacji, a więc można go bez obaw pozostawić w kodzie źródłowym programu. Listing B.1. Programowe dodanie punktu kontrolnego var http = require('http'); function handleRequest(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Witaj, świecie\n'); } http.createServer(function (req, res) { debugger; Dodanie punktu kontrolnego do kodu. handleRequest(req, res); }).listen(1337, '127.0.0.1'); console.log('Serwer jest dostępny pod adresem http://127.0.0.1:1337/');

Jeżeli w trybie debugowania uruchomisz program przedstawiony w listingu B.1, pierwsze wstrzymanie działania nastąpi w wierszu pierwszym. Po wydaniu polecenia cont w debugerze działanie aplikacji będzie kontynuowane do czasu utworzenia serwera i oczekiwania na połączenie. Po nawiązaniu połączenia przez przejście na stronę http://127.0.0.1:1337 w przeglądarce internetowej przekonasz się, że działanie programu zostało wstrzymane w wierszu debugger;. Wydanie polecenia next spowoduje wykonanie kolejnego wiersza kodu. Bieżącym wierszem stanie się wywołanie handleRequest(). Jeżeli znów wydasz polecenie next, aby wykonać kolejny wiersz kodu, debuger nie zejdzie do kolejnego wiersza funkcji handleRequest(). Wydanie polecenia step powoduje przejście do funkcji handleRequest() i pozwala na rozwiązanie wszelkich związanych z nią problemów. Gdy zmienisz zdanie dotyczące debugowania funkcji handleRequest(), wydaj polecenie out (lub o), co spowoduje opuszczenie funkcji.

Punkty kontrolne nie muszą być definiowane jedynie w kodzie źródłowym, można je ustawić także z poziomu debugera. Aby w debugerze ustawić punkt kontrolny w bieżącym wierszu, należy wydać polecenie setBreakpoint() (lub sb()). Istnieje także możliwość ustawienia punktu kontrolnego we wskazanym wierszu (sb(wiersz)) lub w konkretnej funkcji (sb('fn()')). Kiedy

chcesz

usunąć punkt kontrolny, do dyspozycji masz funkcję clearBreakpoint() (cb()). Wymieniona funkcja pobiera takie same argumenty jak setBreakpoint(), ale stosuje je w przeciwnym celu.

B.3.2. Analiza i zmiana stanu w debugerze Jeżeli chcesz monitorować określone wartości w aplikacji, możesz dodać tak zwanych wartowników. Informują oni o wartości zmiennej, gdy poruszasz się po kodzie. Na przykład podczas debugowania kodu przedstawionego w listingu B.1 możesz wydać polecenie watch ("req.headers['user-agent']"). W każdym kroku będziesz widział, jaka przeglądarka internetowa wykonała żądanie. Wyświetlenie listy wartowników następuje po wydaniu polecenia watchers. Aby usunąć wartownika, należy użyć polecenia unwatch, na przykład unwatch("req .headers['user- agent']"). Jeżeli na jakimkolwiek etapie procesu debugowania będziesz chciał w pełni przeanalizować lub zmienić stan, wówczas możesz użyć polecenia repl, aby przejść do interfejsu REPL. W ten sposób zyskujesz możliwość podania dowolnego wyrażenia JavaScript i zostanie ono obliczone. Opuszczenie interfejsu REPL i powrót do debugera następuje po naciśnięciu klawiszy Ctrl+C. Po zakończeniu debugowania opuszczenie debugera następuje po dwukrotnym naciśnięciu klawiszy Ctrl+C, naciśnięciu Ctrl+D lub po wydaniu polecenia .exit. Tutaj przedstawiono jedynie podstawy użycia debugera. Więcej informacji na temat tego, co można zrobić za pomocą debugera, znajdziesz na stronie http://nodejs.org/api/debugger.html.

B.4. Inspektor Node Inspektor Node to alternatywa dla wbudowanego debugera Node. Jest używany przez przeglądarki internetowe zbudowane w oparciu o silnik WebKit, na przykład Chrome i Safari, a nie dostępny jako interfejs z poziomu powłoki.

B.4.1. Uruchomienie inspektora Node Zanim rozpoczniesz debugowanie, powinieneś zainstalować inspektora Node

globalnie za pomocą przedstawionego poniżej polecenia. Po instalacji w systemie będzie dostępne polecenie node-inspector: npm install -g node-inspector

W celu rozpoczęcia debugowania aplikacji Node uruchom ją z opcją powłoce:

--debug-brk

w

node --debug-brk server.js

Użycie opcji --debug-brk powoduje, że proces debugowania wstawi punkt kontrolny przed pierwszym wierszem aplikacji. Jeżeli jest to niepożądane, wtedy zamiast wymienionej można użyć opcji --debug. Po uruchomieniu aplikacji należy uruchomić inspektora Node: node-inspector

Inspektor Node jest interesujący, ponieważ używa tego samego kodu co inspektor sieciowy WebKit. Zostaje jednak umieszczony w silniku JavaScript Node, więc programiści sieciowi powinni czuć się jak w domu podczas jego użycia. Po uruchomieniu inspektora Node przejdź na stronę http://127.0.0.1:8080/debug?port=5858 w przeglądarce internetowej opartej na silniku WebKit, a zobaczysz inspektora. Jeżeli uruchomiłeś go za pomocą opcji --debug-brk, inspektor Node będzie natychmiast wyświetlał pierwszy skrypt aplikacji, jak pokazano na rysunku B.2. W przypadku użycia opcji --debug będziesz miał możliwość wyboru skryptu, co na rysunku B.2 jest wskazywane przez nazwę step.js. W ten sposób możesz wybrać skrypt, który będzie debugowany. Czerwona strzałka pokazana po lewej stronie kodu wskazuje wiersz, który zostanie wykonany jako kolejny.

B.4.2. Nawigacja po inspektorze Node W celu przejścia do kolejnego wywołania funkcji w aplikacji kliknij przycisk wyglądający jak mała zakręcona strzałka. Inspektor Node, podobnie jak debuger Node działający w powłoce, pozwala na wejście do funkcji. Kiedy czerwona strzałka po lewej stronie wiersza kodu wskazuje wywołanie funkcji, wejście do funkcji następuje po kliknięciu małej strzałki skierowanej w dół i wskazującej kółko. Opuszczenie funkcji następuje po kliknięciu przycisku ze strzałką skierowaną w górę. Jeżeli używasz modułów Node wbudowanych lub utworzonych przez społeczność, debuger spowoduje przejście do pliku skryptu modułu, gdy będziesz poruszał się po aplikacji. Nie przejmuj się, w pewnym momencie powrócisz do kodu aplikacji.

Rysunek B.2. Inspektor Node

Aby dodać punkt kontrolny podczas użycia inspektora Node, wystarczy kliknąć numer wiersza po lewej stronie dowolnego wiersza skryptu. Usunięcie wszystkich punktów kontrolnych następuje po kliknięciu przycisku znajdującego się po prawej stronie przycisku opuszczenia funkcji (przekreślona strzałka). Inspektor Node ma również interesującą funkcję, jaką jest możliwość zmiany kodu podczas działania aplikacji. Jeżeli chcesz zmienić wiersz kodu, po prostu dwukrotnie go kliknij, przeprowadź edycję, a następnie kliknij w dowolnym miejscu poza zmodyfikowanym wierszem kodu.

B.4.3. Przeglądanie stanu w inspektorze Node Podczas debugowania aplikacji jej stan można sprawdzać za pomocą rozwijanych paneli znajdujących się pod przyciskami, jak pokazano na rysunku B.3. Wspomniane panele pozwalają na przejrzenie stosu wywołań, zmiennych w zasięgu aktualnie wykonywanego kodu. Modyfikacja zmiennej jest możliwa po jej dwukrotnym kliknięciu i zmianie wartości. Podobnie jak jest w przypadku debugera wbudowanego w Node i działającego w powłoce, istnieje możliwość dodania wartowników wyświetlających wartości podczas poruszania się po aplikacji. Więcej informacji na temat maksymalnego wykorzystania możliwości oferowanych przez inspektora Node znajdziesz na stronie projektu w serwisie GitHub (https://github.com/node-inspector/node-inspector/).

Rysunek B.3. Przeglądanie stanu aplikacji za pomocą inspektora Node

Jeśli masz wątpliwości, odśwież stronę. Jeżeli podczas użycia inspektora Node zauważysz dziwne zachowanie, może pomóc odświeżenie strony w przeglądarce internetowej. Jeżeli to rozwiązanie nie działa, spróbuj ponownie uruchomić zarówno aplikację, jak i inspektora Node.

Dodatek C Rozszerzenie i konfiguracja frameworka Express Framework Express standardowo oferuje wiele użytecznych funkcji, ale jego rozszerzenie i dostrojenie konfiguracji może uprościć programowanie i zapewnić jeszcze większe możliwości.

C.1. Rozszerzenie frameworka Express Na początek przekonajmy się, jak można rozszerzyć framework Express. W tym podrozdziale dowiesz się: • n Jak utworzyć własne silniki szablonów? • n Jak wykorzystać zalety silników szablonów utworzonych przez społeczność? • n Jak usprawnić aplikacje za pomocą modułów rozszerzających frameworka Express?

C.1.1. Rejestracja szablonów silników Silnik może standardowo zapewniać obsługę frameworka Express przez eksport metody __express. Jednak nie każdy silnik szablonów oferuje tego rodzaju możliwość. Ponadto być może będziesz chciał utworzyć własny silnik. Framework Express jest na to przygotowany i oferuje metodę app.engine(). W tym punkcie dowiesz się, jak utworzyć mały silnik szablonów zapewniający zastępowanie zmiennej, co pozwala na obsługę treści dynamicznej. Metoda app.engine() mapuje rozszerzenie nazwy pliku na funkcję wywołania zwrotnego, aby framework Express „wiedział”, jak należy użyć danego pliku. W listingu C.1 przedstawiono kod, w którym plik z rozszerzeniem .md jest przekazywany, aby wywołanie takie jak res.render('myview.md') stosowało wskazaną funkcję wywołania zwrotnego do wygenerowania pliku. Przedstawiona abstrakcja pozwala na użycie z frameworkiem Express praktycznie każdego silnika szablonów. W omawianym silniku szablonów nawiasy są używane wokół zmiennych lokalnych, aby w ten sposób zapewnić obsługę dynamicznych danych wejściowych. Na przykład {name} spowoduje wyświetlenie wartości name za każdym razem, gdy pojawi się w szablonie. Listing C.1. Obsługa rozszerzenia .md

var express = require('express'); var http = require('http'); var md = require('github-flavored-markdown').parse; Wymagana jest implementacja Markdown. var fs = require('fs'); var app = express(); Mapowanie tego wywołania zwrotnego na pliki z rozszerzeniem .md. app.engine('md', function(path, options, fn){ fs.readFile(path, 'utf8', function(err, str){ Odczyt zawartości pliku i umieszczenie jej w ciągu tekstowym. if (err) return fn(err); try { Przekazanie frameworkowi Express obsługi błędów. var html = md(str); Konwersja ciągu tekstowego w formacie Markdown na kod HTML. html = html.replace(/\{([^}]+)\}/g, function(_, name){ Zastąpienie wartości w nawiasach. return options[name] || ''; Wartość domyślna to '' (pusty ciąg tekstowy). }); fn(null, html); Przekazanie frameworkowi Express wygenerowanego kodu HTML. } catch (err) { fn(err); Przechwycenie wszystkich zgłoszonych błędów. } }); });

Silnik szablonów przedstawiony w listingu C.1 pozwala na tworzenie dynamicznych widoków za pomocą składni Markdown. Jeśli na przykład chcesz powitać użytkownika, możesz użyć następującego kodu: # {name} Witaj, {name}! Cieszymy się, że będziesz używał aplikacji {appName}.

C.1.2. Szablony i projekt consolidate.js Projekt consolidate.js został przygotowany specjalnie dla frameworka Express 3.x i oferuje pojedyncze, ujednolicone API dla wielu silników szablonów w Node. Oznacza to, że standardowo Express 3.x pozwala na użycie ponad 14 różnych silników szablonów. Jeżeli pracujesz nad biblioteką używającą szablonów, wówczas możesz skorzystać z ich szerokiej gamy oferowanej przez consolidate.js. Na przykład Swig to silnik szablonów zainspirowany przez Django. Do zdefiniowania logiki korzysta ze znaczników osadzonych w kodzie HTML, jak

przedstawiono poniżej:
    {% for pet in pets %}
  • {{ pet.name }}
  • {% endfor %}


Prawdopodobnie w zależności od silnika szablonów i edytora tekstów z funkcją podświetlania składni wolisz korzystać z silników opartych na stylu HTML. Wówczas pliki mają rozszerzenie .html, a nie pochodzące od nazwy silnika, na przykład .swig. Z pomocą przychodzi wówczas metoda app.engine() frameworka Express. Po jej wywołaniu wygenerowany przez framework Express plik .html będzie używać wskazanego silnika szablonów, na przykład Swig: var cons = require('consolidate'); app.engine('html', cons.swig);

Silnik szablonów EJS również będzie mapowany na pliki .html, ponieważ też używa osadzonych znaczników:






Pewne silniki szablonów korzystają z zupełnie odmiennej składni, a więc nie ma sensu mapować ich na pliki .html. Dobrym przykładem jest silnik Jade posiadający własny język deklaracyjny. Jade można mapować za pomocą poniższego wywołania: var cons = require('consolidate'); app.engine('jade', cons.jade);

Więcej informacji szczegółowych oraz listę obsługiwanych silników szablonów znajdziesz na stronie repozytorium projektu consolidate.js pod adresem https://github.com/visionmedia/consolidate.js.

C.1.3. Frameworki i rozszerzenia Express Być może zastanawiasz się, jakie opcje mają programiści korzystający z bardziej strukturalnych frameworków, takich jak Ruby on Rails. Express oferuje kilka możliwości w takiej sytuacji. Społeczność Express opracowała wiele działających na wysokim poziomie frameworków opartych na Express w celu dostarczenia struktury katalogów, a

także funkcji, na przykład kontrolerów w stylu Ruby. Poza wspomnianymi frameworkami Express obsługuje także mnóstwo wtyczek rozszerzających jego możliwości standardowe.

Express-Expose W tycz ka express-expose może być wykorzystana do udostępnienia klientowi obiektów JavaScript znajdujących się po stronie serwera. Na przykład jeśli chcesz udostępnić dane JSON uwierzytelnionego użytkownika, wtedy możesz użyć wywołania res.expose() i dostarczyć kodowi działającemu po stronie klienta obiekt express.user: res.expose(req.user, 'express.user');

Express-Resource Inną doskonałą wtyczką jest express-resource, która jest wykorzystywana do obsługi strukturalnego routingu. Routing można zapewnić na wiele sposobów, ale wszystkie praktycznie sprowadzają się do metody żądania i ścieżki, co Express oferuje standardowo. Na tej podstawie można tworzyć koncepcje działające na wysokim poziomie. W przedstawionym poniżej przykładzie pokazano, jak można zdefiniować akcje przeznaczone do pokazywania, tworzenia i uaktualniania zasobu użytkownika w sposób deklaracyjny. Przede wszystkim w pliku app.js trzeba umieścić następujących wiersz kodu: app.resource('user', require('./controllers/user'));

W listingu C.2 przedstawiono kod modułu kontrolera /controllers/user.js. Listing C.2. Plik user.js exports.new = function(req, res){ res.send('new user'); }; exports.create = function(req, res){ res.send('create user'); }; exports.show = function(req, res){ res.send('show user ' + req.params.user); };

Pełną listę wtyczek, silników szablonów i frameworków zamieszczono w wiki frameworka Express na stronie https://github.com/visionmedia/express/wiki.

C.2. Konfiguracja zaawansowana

Z wcześniejszych rozdziałów książki dowiedziałeś się, jak skonfigurować framework Express za pomocą funkcji app.configure(). Przedstawiono w nich również wiele opcji konfiguracyjnych. W tym podrozdziale poznasz dodatkowe opcje konfiguracyjne, które można wykorzystać do zmiany zachowania domyślnego oraz udostępnienia kolejnych funkcji. W tabeli C.1 wymieniono opcje konfiguracyjne frameworka Express, które nie zostały omówione w rozdziale 8. Tabela C.1. Wbudowane ustawienia Express default engine

Użyty domyślny silnik szablonu

views

Wyświetlenie ścieżki wyszukiwania

json replacer

Funkcja modyfikacji odpowiedzi JSON

json spaces

Liczba spacji użytych do formatowania odpowiedzi JSON

jsnop callback

Obsługa JSONP za pomocą res.json() i res.send()

trust proxy

Zaufanie odwróconemu proxy

view cache

Funkcje buforowania silnika szablonu

Opcja konfiguracyjna views jest całkiem prosta. Używa się jej do wskazania lokalizacji szablonów widoku. Kiedy tworzysz szkielet aplikacji z poziomu powłoki za pomocą polecenia express, opcja konfiguracyjna view automatycznie wskazuje podkatalog views aplikacji. Teraz przechodzimy do nieco bardziej skomplikowanej opcji konfiguracyjnej, czyli json_replacer.

C.2.1. Modyfikacja odpowiedzi JSON Przyjmujemy założenie, że masz obiekt user wraz z właściwościami prywatnymi, takimi jak identyfikator obiektu _id. Domyślnie wywołanie metody res.send(user) spowoduje udzielenie odpowiedzi w formacie JSON w postaci takiej jak {"_id":123,"name":"Tobi"}. Opcja json replacer to ustawienie pobierające funkcję, którą framework Express przekaże JSON.stringify() w trakcie wywołań res.send() i res.json(). Samodzielna aplikacja Express przedstawiona w listingu C.3 pokazuje, jak można wykorzystać wymienioną opcję do pominięcia w odpowiedzi JSON wszystkich właściwości o nazwach rozpoczynających się od znaku podkreślenia. W omawianym przykładzie odpowiedź będzie miała postać {"name":"Tobi"}. Listing C.3. Użycie json_replacer do kontroli i modyfikacji danych JSON var express = require('express'); var app = express();

app.set('json replacer', function(key, value){ if ('_' == key[0]) return; return value; }); var user = { _id: 123, name: 'Tobi' }; app.get('/user', function(req, res){ res.send(user); }); app.listen(3000);

Zwróć uwagę, implementować

że poszczególne obiekty lub prototypy obiektów mogą metodę toJSON(). Wymieniona metoda jest używana przez JSON.stringify() podczas konwersji obiektu na ciąg tekstowy JSON. To jest doskonała alternatywa dla wywołania zwrotnego json_replacer, jeśli wprowadzane zmiany nie dotyczą każdego obiektu. W ten sposób dowiedziałeś się, jak określać dane, które powinny znajdować się w danych wyjściowych JSON. Możemy więc przejść do dostrajania formatowania danych w formacie JSON.

C.2.2. Formatowanie odpowiedzi JSON Opcja konfiguracyjna json spaces wpływa na wywołania JSON.stringify() we frameworku Express. Wymieniona opcja definiuje liczbę spacji używanych podczas formatowania danych JSON jako ciągu tekstowego. Domyślnie

metoda zwraca skompresowane dane JSON, na przykład {"name":"Tobi",å"age":2,"species":"zwierzę"}. Tego rodzaju skompresowane dane JSON są idealne w środowisku produkcyjnym, ponieważ zmniejszają wielkość udzielanej odpowiedzi. Jednak w trakcie prac nad aplikacją nieskompresowane dane wyjściowe są znacznie łatwiejsze w odczycie. O pc j a json spaces ma automatycznie ustawianą wartość 0 w środowisku produkcyjnym oraz 2 w środowisku programistycznym, co powoduje wygenerowanie danych wyjściowych w następującej postaci: { "name": "Tobi", "age": 2, "species": "zwierzę" }

C.2.3. Zaufanie nagłówkom odwrotnego proxy

Domyślnie framework Express w żadnym środowisku „nie ufa” nagłówkom odwrotnego proxy. Wspomniane odwrotne nagłówki proxy wykraczają poza zakres tematyczny tej książki. Jeśli Twoja aplikacja działa za odwrotnym proxy, na przykład Nginx, HAProxy lub Varnish, wtedy powinieneś użyć opcji trust proxy , aby framework Express „wiedział”, że te nagłówki można bezpiecznie sprawdzać.

Spis treści Wstęp Przedmowa Podziękowania Mike Cantelon Marc Harter Nathan Rajlich O książce Mapa drogowa Konwencje zastosowane w kodzie i materiały do pobrania Forum Author Online Część I Podstawy Node Rozdział 1. Witamy w Node.js 1.1. Node jest zbudowane w oparciu o JavaScript 1.2. Asynchroniczna i oparta na zdarzeniach: przeglądarka internetowa 1.3. Asynchroniczny i oparty na zdarzeniach: serwer 1.4. Aplikacje DIRT 1.5. Domyślna aplikacja jest typu DIRT 1.5.1. Prosty przykład aplikacji asynchronicznej 1.5.2. Serwer HTTP 1.5.3. Strumieniowanie danych 1.6. Podsumowanie Rozdział 2. Tworzenie aplikacji wielopokojowego czatu 2.1. Ogólny opis aplikacji 2.2. Wymagania aplikacji i konfiguracja początkowa 2.2.1. Obsługa HTTP i WebSocket 2.2.2. Tworzenie struktury plików aplikacji 2.2.3. Wskazanie zależności 2.2.4. Instalacja zależności 2.3. Udostępnianie plików HTML, CSS i kodu JavaScript działającego po stronie klienta 2.3.1. Tworzenie podstawowego serwera plików statycznych Wysyłanie danych pliku i odpowiedzi w postaci błędów Tworzenie serwera HTTP Uruchomienie serwera HTTP 2.3.2. Dodanie plików HTML i CSS 2.4. Obsługa wiadomości czatu za pomocą biblioteki Socket.IO 2.4.1. Konfiguracja serwera Socket.IO Utworzenie logiki połączenia 2.4.2. Obsługa zdarzeń oraz scenariuszy w aplikacji

Przypisanie nazwy gościa Dołączanie do pokoju Obsługa żądań zmiany nazwy użytkownika Wysyłanie wiadomości czatu Tworzenie pokoju Obsługa rozłączenia użytkownika 2.5. Użycie kodu JavaScript działającego po stronie klienta do utworzenia interfejsu użytkownika aplikacji 2.5.1. Przekazywanie do serwera wiadomości oraz żądań zmiany pokoju lub nazwy użytkownika 2.5.2. Wyświetlenie w interfejsie użytkownika wiadomości i listy dostępnych pokoi 2.6. Podsumowanie Rozdział 3. Podstawy programowania w Node 3.1. Organizacja i wielokrotne użycie kodu Node 3.1.1. Tworzenie modułu 3.1.2. Dostrajanie tworzenia modułu za pomocą module.exports 3.1.3. Wielokrotne użycie modułów za pomocą katalogu node_modules 3.1.4. Zastrzeżenia 3.2. Techniki programowania asynchronicznego 3.2.1. Użycie wywołań zwrotnych do obsługi zdarzeń jednorazowych 3.2.2. Użycie emitera zdarzeń do obsługi powtarzających się zdarzeń Przykład emitera zdarzeń Udzielanie odpowiedzi na zdarzenie, które powinno wystąpić tylko jednokrotnie Tworzenie emitera zdarzeń — przykład oparty na publikacji i subskrypcji Rozbudowa emitera zdarzeń — przykład obserwatora pliku 3.2.3. Wyzwania pojawiające się podczas programowania asynchronicznego 3.3. Sekwencja logiki asynchronicznej 3.3.1. Kiedy stosować szeregową kontrolę przepływu? 3.3.2. Implementacja szeregowej kontroli przepływu 3.3.3. Implementacja równoległej kontroli przepływu 3.3.4. Użycie narzędzi opracowanych przez społeczność 3.4. Podsumowanie Część II Tworzenie aplikacji sieciowych w Node Rozdział 4. Tworzenie aplikacji sieciowej w Node 4.1. Podstawy dotyczące serwera HTTP 4.1.1. Jak przychodzące żądania HTTP są przez Node przedstawiane programiście? 4.1.2. Prosty serwer HTTP odpowiadający komunikatem „Witaj, świecie” 4.1.3. Odczyt nagłówków żądania i zdefiniowanie nagłówków odpowiedzi 4.1.4. Ustawienie kodu stanu odpowiedzi HTTP 4.2. Tworzenie usługi sieciowej RESTful 4.2.1. Tworzenie zasobów za pomocą żądań POST

4.2.2. Pobieranie zasobów za pomocą żądania GET Zdefiniowanie nagłówka Content-Length 4.2.3. Usunięcie zasobu za pomocą żądania DELETE 4.3. Udostępnianie plików statycznych 4.3.1. Tworzenie serwera plików statycznych Optymalizacja transferu danych za pomocą Stream#pipe() 4.3.2. Obsługa błędów serwera 4.3.3. Wyprzedzająca obsługa błędów za pomocą wywołania fs.stat() 4.4. Akceptacja danych wejściowych użytkownika przekazanych za pomocą formularza sieciowego 4.4.1. Obsługa wysłanych pól formularza sieciowego Moduł querystring 4.4.2. Obsługa przekazanych plików za pomocą formidable 4.4.3. Sprawdzanie postępu operacji przekazywania plików 4.5. Zabezpieczanie aplikacji dzięki użyciu protokołu HTTPS 4.6. Podsumowanie Rozdział 5. Przechowywanie danych aplikacji Node 5.1. Niewymagający serwera magazyn danych 5.1.1. Magazyn danych w pamięci 5.1.2. Magazyn danych oparty na plikach Utworzenie logiki początkowej Zdefiniowanie funkcji pomocniczej do pobierania zadań Zdefiniowanie funkcji pomocniczej do przechowywania zadań 5.2. System zarządzania relacyjną bazą danych 5.2.1. MySQL Użycie MySQL do utworzenia aplikacji śledzącej wykonywanie zadań Utworzenie logiki aplikacji Tworzenie funkcji pomocniczych odpowiedzialnych za wysyłanie kodu HTML, tworzenie formularzy sieciowych i pobieranie danych z formularzy Dodanie danych za pomocą MySQL Usuwanie danych MySQL Uaktualnienie danych MySQL Pobieranie danych MySQL Generowanie rekordów MySQL Generowanie formularzy HTML Wypróbowanie aplikacji 5.2.2. PostgreSQL Nawiązanie połączenia z PostgreSQL Wstawienie rekordu do tabeli bazy danych Utworzenie zapytania zwracającego wynik 5.3. Bazy danych typu NoSQL 5.3.1. Redis Nawiązywanie połączenia z serwerem Redis

Praca z danymi bazy Redis Przechowywanie i pobieranie wartości za pomocą tabeli hash Przechowywanie i pobieranie danych za pomocą listy Przechowywanie i pobieranie danych za pomocą zbiorów Dostarczanie danych za pomocą kanałów Maksymalizacja wydajności Node_Redis 5.3.2. MongoDB Nawiązanie połączenia z MongoDB Uzyskanie dostępu do kolekcji MongoDB Wstawienie dokumentu do kolekcji Uaktualnienie danych za pomocą identyfikatora dokumentu Wyszukiwanie dokumentów Usuwanie dokumentów 5.3.3. Mongoose Otworzenie i zamknięcie połączenia Rejestracja schematu Dodanie zadania Wyszukiwanie dokumentu Uaktualnianie dokumentu Usuwanie dokumentu 5.4. Podsumowanie Rozdział 6. Framework Connect 6.1. Konfiguracja aplikacji Connect 6.2. Jak działa metoda pośrednicząca frameworka Connect? 6.2.1. Metody pośredniczące wyświetlające żądanie 6.2.2. Metoda pośrednicząca udzielająca odpowiedzi w postaci komunikatu „Witaj, świecie” 6.3. Dlaczego kolejność metod pośredniczących ma znaczenie? 6.3.1. Kiedy metoda pośrednicząca nie wywołuje next()? 6.3.2. Użycie kolejności metod pośredniczących do przeprowadzenia uwierzytelnienia 6.4. Montowanie metody pośredniczącej i serwera 6.4.1. Metody pośredniczące przeprowadzające uwierzytelnianie 6.4.2. Metoda pośrednicząca wyświetlająca panel administracyjny Przetestowanie całości 6.5. Tworzenie konfigurowalnej metody pośredniczącej 6.5.1. Tworzenie konfigurowalnej metody pośredniczącej logger() 6.5.2. Tworzenie metody pośredniczącej router() 6.5.3. Tworzenie metody pośredniczącej przeznaczonej do przepisywania adresów URL 6.6. Użycie metody pośredniczącej do obsługi błędów 6.6.1. Domyślna obsługa błędów w Connect 6.6.2. Samodzielna obsługa błędów aplikacji

6.6.3. Użycie wielu metod pośredniczących przeznaczonych do obsługi błędów Implementacja metody pośredniczącej hello() Implementacja metody pośredniczącej users() Implementacja metody pośredniczącej pets() Implementacja metody pośredniczącej errorHandler() Implementacja metody pośredniczącej errorPage 6.7. Podsumowanie Rozdział 7. Metody pośredniczące frameworka Connect 7.1. Metody pośredniczące przeznaczone do przetwarzania plików cookie, danych żądań i ciągów tekstowych zapytań 7.1.1. cookieParser() — przetwarzanie plików cookie Podstawowy sposób użycia Zwykłe cookie Podpisane cookie Cookie w formacie JSON Ustawienie cookie wychodzących 7.1.2. bodyParser() — przetwarzanie danych żądania Podstawowy sposób użycia Przetwarzanie danych JSON Przetwarzanie zwykłych danych formularza sieciowego Przetwarzanie wieloczęściowych danych formularza sieciowego 7.1.3. limit() — ograniczenie danych żądania Dlaczego metoda pośrednicząca limit() jest potrzebna? Podstawowy sposób użycia Opakowanie metody pośredniczącej limit() w celu uzyskania większej elastyczności 7.1.4. query() — analizator ciągu tekstowego zapytania Podstawowy sposób użycia 7.2. Metody pośredniczące implementujące podstawowe funkcje wymagane przez aplikację sieciową 7.2.1. logger() — rejestracja informacji o żądaniu Podstawowy sposób użycia Dostosowanie do własnych potrzeb formatu rejestracji danych Opcje metody pośredniczącej logger() — stream, immediate i buffer 7.2.2. favicon() — obsługa ikon favicon Podstawowy sposób użycia 7.2.3. methodOverride() — nieprawdziwe metody HTTP Podstawowy sposób użycia Uzyskanie dostępu do oryginalnej właściwości req.method 7.2.4. vhost() — wirtualny hosting Podstawowy sposób użycia Użycie wielu egzemplarzy vhost() 7.2.5. session() — zarządzanie sesją

Podstawowy sposób użycia Ustawienie czasu wygaśnięcia ważności sesji Praca z danymi sesji Praca z plikami cookie sesji Magazyn danych sesji 7.3. Metody pośredniczące zapewniające bezpieczeństwo aplikacji sieciowej 7.3.1. basicAuth() — uwierzytelnianie podstawowe HTTP Podstawowy sposób użycia Przekazanie funkcji wywołania zwrotnego Przekazanie asynchronicznej funkcji wywołania zwrotnego Przykład z użyciem polecenia curl 7.3.2. csrf() — ochrona przed atakami typu CSRF Podstawowy sposób użycia 7.3.3. errorHandler() — obsługa błędów w trakcie tworzenia aplikacji Podstawowy sposób użycia Komunikat błędu w formacie HTML Komunikat błędu w formacie zwykłego tekstu Komunikat błędu w formacie JSON 7.4. Metody pośredniczące przeznaczone do udostępniania plików statycznych 7.4.1. static() — udostępnianie plików statycznych Podstawowy sposób użycia Użycie metody static() wraz z montowaniem Bezwzględne kontra względne ścieżki dostępu do katalogów Udostępnianie pliku index.html, gdy żądanie dotyczy katalogu 7.4.2. compress() — kompresja plików statycznych Podstawowy sposób użycia Użycie własnej funkcji filtrującej Określenie poziomu kompresji i pamięci 7.4.3. directory() — wyświetlenie katalogów Podstawowy sposób użycia Podstawowy sposób użycia 7.5. Podsumowanie Rozdział 8. Framework Express 8.1. Utworzenie szkieletu aplikacji 8.1.1. Globalna instalacja frameworka Express 8.1.2. Generowanie aplikacji 8.1.3. Poznawanie aplikacji 8.2. Konfiguracja frameworka Express i tworzonej aplikacji 8.2.1. Konfiguracja na podstawie środowiska 8.3. Generowanie widoków aplikacji Express 8.3.1. Konfiguracja systemu widoków Zmiana katalogu wyszukiwania Domyślny silnik szablonów

Buforowanie widoku 8.3.2. Wyszukiwanie widoku 8.3.3. Udostępnianie danych widokom Tworzenie widoku wyświetlającego listę zdjęć Metody udostępniania danych widokom 8.4. Obsługa formularzy i przekazywania plików 8.4.1. Implementacja modelu zdjęcia 8.4.2. Tworzenie formularza przeznaczonego do przekazywania zdjęć Utworzenie formularza Dodanie trasy dla strony przeznaczonej do przekazywania zdjęć Obsługa wysyłania danych formularza 8.4.3. Wyświetlenie listy przekazanych zdjęć 8.5. Obsługa pobierania zasobów 8.5.1. Tworzenie trasy dla pobierania zdjęć 8.5.2. Implementacja trasy pobierania zdjęcia Rozpoczęcie pobierania przez przeglądarkę Ustawienie nazwy pobieranego pliku 8.6. Podsumowanie Rozdział 9. Zaawansowane użycie frameworka Express 9.1. Uwierzytelnianie użytkowników 9.1.1. Zapisywanie i wczytywanie użytkowników Tworzenie pliku package.json Tworzenie modelu User Zapis użytkownika w bazie danych Redis Zabezpieczanie hasła użytkownika Testowanie logiki odpowiedzialnej za zapis użytkownika Pobieranie danych użytkownika Uwierzytelnianie użytkownika 9.1.2. Rejestrowanie nowego użytkownika Dodanie tras rejestracji Tworzenie formularza rejestracji Udzielanie odpowiedzi użytkownikowi Trwałe przechowywanie komunikatów w sesji Implementacja rejestracji użytkownika 9.1.3. Logowanie zarejestrowanych użytkowników Wyświetlenie formularza logowania Uwierzytelnienie logowania Utworzenie menu dla użytkowników uwierzytelnionych i anonimowych 9.1.4. Metoda pośrednicząca przeznaczona do wczytywania użytkownika 9.2. Zaawansowane techniki routingu 9.2.1. Weryfikacja użytkownika podczas przesyłania treści Utworzenie modelu postu Dodanie tras powiązanych z postami

Dodanie strony wyświetlającej listę postów Utworzenie formularza postu Implementacja tworzenia postu 9.2.2. Metoda pośrednicząca charakterystyczna dla trasy Weryfikacja formularza za pomocą metody pośredniczącej przeznaczonej dla trasy Utworzenie elastycznej metody pośredniczącej przeznaczonej do przeprowadzania weryfikacji 9.2.3. Implementacja stronicowania Projektowanie API stronicowania Implementacja metody pośredniczącej przeznaczonej do obsługi stronicowania Użycie stronicowania w trasie Utworzenie szablonu dla łączy mechanizmu stronicowania Dodanie łączy stronicowania w szablonie Włączenie czystych adresów URL dla stronicowania 9.3. Utworzenie publicznego API REST 9.3.1. Projekt API 9.3.2. Dodanie uwierzytelnienia podstawowego 9.3.3. Implementacja routingu Testowanie operacji pobierania danych użytkownika Usunięcie danych wrażliwych użytkownika Dodanie postów Dodanie obsługi wyświetlania listy wpisów 9.3.4. Włączenie negocjacji treści Implementacja negocjacji treści Udzielenie odpowiedzi w postaci XML 9.4. Obsługa błędów 9.4.1. Obsługa błędów 404 Dodanie trasy pozwalającej na udzielenie odpowiedzi informującej o błędzie Tworzenie szablonu dla strony błędu Włączenie metody pośredniczącej 9.4.2. Obsługa błędów Użycie trasy warunkowej do przetestowania stron błędów Implementacja procedury obsługi błędów Utworzenie szablonu strony błędu Włączenie metody pośredniczącej 9.5. Podsumowanie Rozdział 10. Testowanie aplikacji Node 10.1. Testy jednostkowe 10.1.1. Moduł assert Prosty przykład Użycie asercji eqal() do sprawdzenia wartości zmiennej Użycie asercji notEqual() do wyszukiwania problemów w logice

Użycie asercji dodatkowych strictEqual(), notStrictEqual(), deepEqual(), notDeepEqual() Użycie asercji ok() do sprawdzenia, czy wartość zwrotna metody asynchronicznej wynosi true Sprawdzenie, czy zgłaszane komunikaty błędów są poprawne Dodanie logiki przeznaczonej do uruchamiania testów 10.1.2. Framework nodeunit Instalacja nodeunit Testowanie aplikacji Node za pomocą frameworka nodeunit 10.1.3. Mocha Testowanie aplikacji Node za pomocą Mocha Zdefiniowanie konfiguracji i czyszczenie logiki za pomocą zaczepów Mocha Testowanie logiki asynchronicznej 10.1.4. Framework Vows Testowanie logiki aplikacji za pomocą frameworka Vows 10.1.5. Biblioteka should.js Testowanie funkcjonalności modułu za pomocą biblioteki should.js 10.2. Testy akceptacyjne 10.2.1. Tobi Testowanie aplikacji sieciowych za pomocą Tobi 10.2.2. Soda Instalacja frameworka Soda i serwera Selenium Testowanie aplikacji sieciowej za pomocą Soda i Selenium Testowanie aplikacji sieciowej za pomocą Soda i Sauce Labs 10.3. Podsumowanie Rozdział 11. Szablony w aplikacji sieciowej 11.1. Użycie szablonów w celu zachowania przejrzystości kodu 11.1.1. Szablon w akcji Wygenerowanie kodu HTML bez użycia szablonu Wygenerowanie kodu HTML z użyciem szablonu 11.2. Silnik szablonów Embedded JavaScript 11.2.1. Tworzenie szablonu Zmiana znaczenia znaków 11.2.2. Praca z danymi szablonu za pomocą filtrów EJS Filtry obsługujące wybór Filtry przeznaczone do zmiany wielkości znaków Filtry przeznaczone do pracy z tekstem Filtry przeprowadzające sortowanie Filtr map Tworzenie własnych filtrów 11.2.3. Integracja EJS w aplikacji Buforowanie szablonów EJS 11.2.4. Użycie EJS w aplikacjach działających po stronie klienta

11.3. Użycie języka szablonów Mustache wraz z silnikiem Hogan 11.3.1. Tworzenie szablonu 11.3.2. Znaczniki Mustache Wyświetlanie prostych wartości Sekcje: iteracja przez wiele wartości Sekcje odwrócone: domyślny kod HTML, gdy wartość nie istnieje Sekcja lambda: własna funkcjonalność w blokach sekcji Partials: wielokrotne użycie szablonów w innych szablonach 11.3.3. Dostosowanie szablonu Hogan do własnych potrzeb 11.4. Szablony Jade 11.4.1. Podstawy szablonów Jade Podawanie atrybutów znacznika Podanie treści znacznika Zachowanie organizacji dzięki rozwinięciu bloku Umieszczanie danych w szablonach Jade 11.4.2. Logika w szablonach Jade Użycie JavaScript w szablonach Jade Iteracja przez obiekty i tablice Warunkowe wygenerowanie kodu szablonu Użycie poleceń case w Jade 11.4.3. Organizacja szablonów Jade Strukturyzacja wielu szablonów za pomocą ich dziedziczenia Implementacja układu za pomocą poprzedzenia blokiem lub dołączenia bloku Dołączanie szablonu Wielokrotne użycie logiki szablonu za pomocą polecenia mixin 11.5. Podsumowanie Część III Co dalej? Rozdział 12. Wdrażanie aplikacji Node i zapewnienie bezawaryjnego działania 12.1. Hosting aplikacji Node 12.1.1. Serwery dedykowane i VPS 12.1.2. Hosting w chmurze Amazon Web Services Rackspace Cloud 12.2. Podstawy wdrożenia 12.2.1. Wdrożenie z repozytorium Git 12.2.2. Zapewnienie działania aplikacji Node 12.3. Maksymalizacja wydajności i czasu bezawaryjnego działania aplikacji 12.3.1. Zapewnienie działania aplikacji za pomocą Upstart 12.3.2. API klastra — wykorzystanie zalety w postaci wielu rdzeni 12.3.3. Proxy i hosting plików statycznych 12.4. Podsumowanie Rozdział 13. Nie tylko serwery WWW 13.1. Biblioteka Socket.IO

13.1.1. Tworzenie minimalnej aplikacji Socket.IO Wypróbowanie aplikacji 13.1.2. Użycie biblioteki Socket.IO do odświeżenia strony i stylów CSS Wypróbowanie aplikacji 13.1.3. Inne zastosowania dla biblioteki Socket.IO 13.2. Dokładniejsze omówienie sieci TCP/IP 13.2.1. Praca z buforami i danymi binarnymi Dane tekstowe kontra binarne 13.2.2. Tworzenie serwera TCP Zapis danych Odczyt danych Połączenie dwóch strumieni za pomocą socket.pipe() Obsługa nieeleganckiego zamknięcia połączenia Zebranie wszystkiego w całość 13.2.3. Tworzenie klienta TCP 13.3. Narzędzia przeznaczone do pracy z systemem operacyjnym 13.3.1. Obiekt process, czyli globalny wzorzec Singleton Użycie process.env do pobierania i ustawiania zmiennych środowiskowych Zdarzenia specjalne emitowane przez obiekt proces Przechwytywanie sygnałów wysyłanych procesowi 13.3.2. Użycie modułu filesystem Przenoszenie pliku Monitorowanie katalogu lub pliku pod kątem zmian Użycie opracowanych przez społeczność modułów fstream i filed 13.3.3. Tworzenie procesów zewnętrznych Buforowanie za pomocą cp.exec() wyników działania polecenia Tworzenie poleceń za pomocą interfejsu Stream i cp.spawn() Rozkład obciążenia za pomocą cp.fork() 13.4. Tworzenie narzędzi powłoki 13.4.1. Przetwarzanie argumentów podanych w powłoce 13.4.2. Praca ze standardowym wejściem i wyjściem Zapis danych wyjściowych za pomocą process.stdout Odczyt danych wejściowych za pomocą process.stdin Rejestracja danych diagnostycznych za pomocą process.stderr 13.4.3. Dodanie koloru do danych wyjściowych Tworzenie i zapis znaków sterujących ANSI Formatowanie koloru tekstu za pomocą ansi.js Formatowanie koloru tła za pomocą ansi.js 13.5. Podsumowanie Rozdział 14. Ekosystem Node 14.1. Dostępne w internecie zasoby dla programistów Node 14.1.1. Node i odniesienia do modułów 14.1.2. Grupy Google

14.1.3. IRC 14.1.4. Zgłaszanie problemów w serwisie GitHub 14.2. Serwis GitHub 14.2.1. Rozpoczęcie pracy z GitHub Konfiguracja Git i rejestracja GitHub Dostarczenie GitHub klucza publicznego SSH 14.2.2. Dodanie projektu do GitHub Utworzenie repozytorium GitHub Konfiguracja pustego repozytorium Git Dodanie plików do repozytorium Git Przekazanie repozytorium Git do serwisu GitHub 14.2.3. Współpraca przez serwis GitHub 14.3. Przekazanie własnego modułu do repozytorium npm 14.3.1. Przygotowanie pakietu 14.3.2. Przygotowanie specyfikacji pakietu 14.3.3. Testowanie i publikowanie pakietu Testowanie instalacji pakietu Dodanie użytkownika npm Publikacja w repozytorium npm 14.4. Podsumowanie Dodatek A Instalacja Node i dodatki opracowane przez społeczność A.1. Instalacja w systemie OS X A.1.1. Instalacja za pomocą Homebrew A.2. Instalacja w systemie Windows A.3. Instalacja w systemie Linux A.3.1. Przygotowania do instalacji w Ubuntu A.3.2. Przygotowania do instalacji w CentOS A.4. Kompilacja Node A.5. Używanie menedżera pakietów Node A.5.1. Wyszukiwanie pakietów A.5.2. Instalacja pakietu A.5.3. Przeglądanie dokumentacji i kodu pakietu Dodatek B Debugowanie Node B.1. Analiza kodu za pomocą JSHint B.2. Dane wyjściowe debugowania B.2.1. Debugowanie za pomocą modułu console Wyświetlanie informacji o stanie aplikacji Dane wyjściowe dotyczące pomiaru czasu Wyświetlanie stosu wywołań B.2.2. Użycie modułu debug do zarządzania danymi wyjściowymi procesu debugowania B.3. Debuger wbudowany w Node B.3.1. Nawigacja po debugerze

B.3.2. Analiza i zmiana stanu w debugerze B.4. Inspektor Node B.4.1. Uruchomienie inspektora Node B.4.2. Nawigacja po inspektorze Node B.4.3. Przeglądanie stanu w inspektorze Node Dodatek C Rozszerzenie i konfiguracja frameworka Express C.1. Rozszerzenie frameworka Express C.1.1. Rejestracja szablonów silników C.1.2. Szablony i projekt consolidate.js C.1.3. Frameworki i rozszerzenia Express Express-Expose Express-Resource C.2. Konfiguracja zaawansowana C.2.1. Modyfikacja odpowiedzi JSON C.2.2. Formatowanie odpowiedzi JSON C.2.3. Zaufanie nagłówkom odwrotnego proxy