1. Informacje ogólne
Plik PDF z tego dokumentu jest dostępny |TUTAJ|.
1.1. Cele
W niniejszym opracowaniu zamierzamy zapoznać się z metodą programowania o nazwie STRUTS. Jakarta Struts to projekt Apache Software Foundation (www.apache.org), którego celem jest zapewnienie standardowego frameworka do tworzenia aplikacji internetowych w języku Java, zgodnego z architekturą zwaną MVC (Model-Widok-Kontroler).
1.2. Model MVC
Model MVC ma na celu rozdzielenie warstw prezentacji, przetwarzania i dostępu do danych. Aplikacja internetowa zgodna z tym modelem będzie miała następującą architekturę:
![]() |
Taka architektura nazywana jest trójwarstwową lub trójpoziomową:
- interfejs użytkownika to V (widok)
- logika aplikacji to C (kontroler)
- źródła danych to M (model)
Interfejsem użytkownika jest często przeglądarka internetowa, ale może to być również aplikacja autonomiczna, która za pośrednictwem sieci wysyła zapytania HTTP do serwisu internetowego i formatuje wyniki, które ten jej zwraca. Logika aplikacji składa się ze skryptów przetwarzających żądania użytkownika. Źródłem danych jest często baza danych, ale mogą to być również proste pliki tekstowe, katalog LDAP, zdalna usługa internetowa... Programista powinien dążyć do zachowania dużej niezależności między tymi trzema elementami, tak aby w przypadku zmiany jednego z nich pozostałe dwa nie musiały ulegać zmianom lub zmiany te były minimalne.
Próbując zastosować ten model w połączeniu z serwletami i stronami JSP, otrzymujemy następującą architekturę:
![]() |
W bloku [Logique Applicative] wyróżniamy
- serwlet, który stanowi punkt wejścia do aplikacji, zwany również kontrolerem
- blok [Classes métier], który zawiera klasy Java niezbędne do realizacji logiki aplikacji.
- blok [Classes d'accès aux données], który zawiera klasy Java niezbędne do pozyskania danych potrzebnych serwletowi, często danych trwałych (BD, pliki, usługa WEB itp.)
- blok stron JSP, tworzący widoki aplikacji.
1.3. Proces tworzenia aplikacji MVC z wykorzystaniem serwletów i stron JSP
Określiliśmy podejście do tworzenia aplikacji internetowych w Javie zgodne z powyższym modelem MVC. Przypominamy je tutaj.
- Zaczniemy od zdefiniowania wszystkich widoków aplikacji. Są to strony internetowe wyświetlane użytkownikowi. Przy projektowaniu widoków przyjmiemy zatem perspektywę użytkownika. Wyróżniamy trzy rodzaje widoków:
- formularz wprowadzania danych, którego celem jest uzyskanie informacji od użytkownika. Zazwyczaj zawiera on przycisk służący do wysłania wprowadzonych danych na serwer.
- strona odpowiedzi, która służy wyłącznie do przekazania informacji użytkownikowi. Często zawiera ona link umożliwiający użytkownikowi kontynuowanie korzystania z aplikacji na innej stronie.
- strona mieszana: serwlet wysłał do klienta stronę zawierającą wygenerowane przez siebie informacje. Ta sama strona posłuży klientowi do przekazania serwletowi dalszych informacji.
- Każdy widok spowoduje wygenerowanie strony o nazwie JSP. Dla każdej z nich:
- określimy wygląd strony
- określi się, które jej części są dynamiczne:
- informacje przeznaczone dla użytkownika, które serwlet musi dostarczyć jako parametry do widoku JSP
- dane wprowadzane przez użytkownika, które muszą zostać przekazane do serwletu w celu przetworzenia. Muszą one stanowić część formularza HTML.
- Można schematycznie przedstawić operacje wejścia/wyjścia dla każdego widoku
![]() |
- dane wejściowe to dane, które serwlet musi dostarczyć do strony JSP, albo w żądaniu (request), albo w sesji (session).
- Wyjścia to dane, które strona JSP musi dostarczyć serwletowi. Stanowią one część formularza HTML, a serwlet pobierze je za pomocą operacji typu request.getparameter(...).
- Napiszemy kod Java/JSP dla każdego widoku. Najczęściej będzie on miał następującą postać:
<%@ page ... %> // najczęściej używane importy klas
<%!
// zmienne instancji strony JSP (=globalne)
// wymagane tylko wtedy, gdy strona JSP zawiera metody współdzielące zmienne (rzadko)
...
%>
<%
// pobieranie danych wysłanych przez serwlet
// zarówno w żądaniu (request), jak i w sesji (session)
...
%>
<html>
...
// w tym miejscu postaramy się zminimalizować kod Java
</html>
- Można teraz przejść do pierwszych testów. Opisana poniżej metoda wdrażania dotyczy serwera Tomcat:
- kontekst aplikacji należy utworzyć w pliku server.xml serwera Tomcat. Można zacząć od przetestowania tego kontekstu. Niech C będzie tym kontekstem, a DC – folderem z nim powiązanym. Utworzymy plik statyczny o nazwie test.html, który umieścimy w folderze DC. Po uruchomieniu serwera Tomcat w przeglądarce wywołamy adres http://localhost:8080/DC/test.html w folderze URL.
- Każdą stronę o nazwie JSP można przetestować. Jeśli strona o nazwie JSP nosi nazwę formulaire.jsp, należy w przeglądarce wywołać stronę URL pod adresem http://localhost:8080/DC/formulaire.jsp. Strona JSP oczekuje wartości od serwletu, który ją wywołuje. W tym przypadku wywołujemy ją bezpośrednio, więc nie otrzyma ona oczekiwanych parametrów. Aby mimo to umożliwić przeprowadzenie testów, na stronie JSP samodzielnie zainicjujemy oczekiwane parametry za pomocą stałych. Te wstępne testy pozwalają sprawdzić, czy strony JSP są poprawne pod względem składniowym.
- Następnie piszemy kod serwletu. Posiada on dwie wyraźnie odrębne metody:
- metodę init, która służy do:
- pobrania parametrów konfiguracyjnych aplikacji z pliku web.xml
- ewentualnego utworzenia instancji klas biznesowych, z których będzie później korzystać
- zarządzanie ewentualną listą błędów inicjalizacji, która zostanie przekazana przyszłym użytkownikom aplikacji. Zarządzanie błędami może obejmować nawet wysłanie wiadomości e-mail do administratora aplikacji w celu poinformowania go o nieprawidłowym działaniu
- metoda doGet lub doPost, w zależności od sposobu, w jaki serwlet odbiera parametry od klientów. Jeśli serwlet obsługuje wiele formularzy, zaleca się, aby każdy z nich wysyłał informację, która jednoznacznie go identyfikuje. Można to osiągnąć za pomocą ukrytego atrybutu w formularzu typu <input type="hidden" name="action" value="...">. Serwlet może najpierw odczytać wartość tego parametru, a następnie przekazać przetwarzanie żądania do wewnętrznej metody prywatnej odpowiedzialnej za obsługę tego typu żądań.
- Należy w miarę możliwości unikać umieszczania kodu biznesowego w serwlecie. Nie jest ona do tego przeznaczona. Serwlet pełni rolę swego rodzaju kierownika zespołu (kontrolera), który odbiera żądania od swoich klientów (użytkowników internetowych) i zleca ich wykonanie najbardziej odpowiednim podmiotom (klasom biznesowym). Podczas pisania serwletu należy określić interfejs klas biznesowych, które mają zostać napisane (konstruktory, metody). Dotyczy to sytuacji, gdy klasy biznesowe muszą zostać stworzone. Jeśli już istnieją, serwlet musi dostosować się do istniejącego interfejsu.
- Kod serwletu zostanie skompilowany.
- Napiszemy szkielet klas biznesowych niezbędnych dla serwletu. Na przykład, jeśli serwlet wykorzystuje obiekt typu proxyArticles, a klasa ta musi posiadać metodę getCodes zwracającą listę (ArrayList) ciągów znaków, na początek wystarczy napisać:
public ArrayList getCodes(){
String[] codes= {"code1","code2","code3"};
ArrayList aCodes=new ArrayList();
for(int i=0;i<codes.length;i++){
aCodes.add(codes[i]);
}
return aCodes;
}
- Można teraz przejść do testowania serwletu.
- Należy utworzyć plik konfiguracyjny aplikacji o nazwie web.xml. Musi on zawierać wszystkie informacje wymagane przez metodę init serwletu (<init-param>). Ponadto należy ustawić URL, za pomocą którego będzie można uzyskać dostęp do głównego serwletu (<servlet-mapping>).
- Wszystkie niezbędne klasy (serwlet, klasy biznesowe) umieszcza się w katalogu WEB-INF/classes.
- Wszystkie niezbędne biblioteki klas (.jar) umieszczono w katalogu WEB-INF/lib. Biblioteki te mogą zawierać klasy biznesowe, sterowniki JDBC itp.
- Widoki JSP umieszcza się w katalogu głównym aplikacji lub w osobnym folderze. Tak samo postępuje się w przypadku pozostałych zasobów (html, obrazy, dźwięk, filmy itp.)
- Po wykonaniu tych czynności aplikacja jest testowana, a pierwsze błędy usuwane. Pod koniec tej fazy architektura aplikacji jest gotowa do działania. Ta faza testowania może być trudna, biorąc pod uwagę, że Tomcat nie posiada narzędzia do debugowania. Wymagałoby to zintegrowania samego Tomcata z narzędziem programistycznym (JBuilder Developer, Sun One Studio itp.). Pomocne mogą być instrukcje System.out.println("...."), które zapisują dane w oknie Tomcata. Pierwszą rzeczą do sprawdzenia jest to, czy metoda init prawidłowo pobiera wszystkie dane z pliku web.xml. W tym celu można wyświetlić wartości tych danych w oknie Tomcata. W ten sam sposób należy sprawdzić, czy metody doGet i doPost poprawnie pobierają parametry z różnych formularzy HTML aplikacji.
Następnie należy napisać klasy biznesowe potrzebne serwletowi. Zazwyczaj jest to klasyczne tworzenie klasy Java, najczęściej niezależnej od jakiejkolwiek aplikacji internetowej. Klasa ta zostanie najpierw przetestowana poza tym środowiskiem, na przykład za pomocą aplikacji konsolowej. Po napisaniu klasy biznesowej można ją zintegrować z architekturą wdrożeniową aplikacji internetowej i przetestować jej prawidłowe działanie w tym środowisku. W ten sposób postępuje się w przypadku każdej klasy biznesowej.
1.4. Podejście programistyczne STRUTS
Twórcy metodologii STRUTS dążyli do zdefiniowania standardowej metody rozwoju, która jest zgodna z architekturą MVC dla aplikacji internetowych napisanych w języku Java. Projekt STRUTS obejmuje dwa aspekty:
- metoda tworzenia oprogramowania. Zobaczymy, że jest ona dość zbliżona do tej opisanej powyżej dla serwletów i stron JSP
- narzędzia, które pozwalają nam stosować tę metodę tworzenia oprogramowania. Są to biblioteki klas Java dostępne na stronie fundacji Apache (www.apache.org).
1.4.1. Metoda tworzenia oprogramowania
Architektura MVC stosowana przez STRUTS wygląda następująco:
![]() |
- kontroler stanowi serce aplikacji. Wszystkie żądania klienta przechodzą przez niego. Jest to serwlet generyczny dostarczany przez STRUTS. W niektórych przypadkach może zaistnieć potrzeba jej rozszerzenia. W prostych przypadkach nie jest to konieczne. Ten serwlet generyczny pobiera potrzebne informacje z pliku, najczęściej nazwanego struts-config.xml.
- Jeśli żądanie klienta zawiera parametry formularza, są one umieszczane w obiekcie typu bean. Klasa jest określana jako typu bean, jeśli przestrzega zasad konstruowania, które omówimy nieco później. Obiekty typu bean tworzone w ten sposób na bieżąco są przechowywane w sesji lub żądaniu klienta. Ta opcja jest konfigurowalna. Nie ma potrzeby ich ponownego tworzenia, jeśli zostały już utworzone.
- W pliku konfiguracyjnym struts-config.html do każdego URL, który ma być przetworzony programowo (a więc nie odpowiada widokowi JSP, który można by wywołać bezpośrednio), przypisuje się pewne informacje:
- nazwę klasy typu Action odpowiedzialnej za przetworzenie żądania. Również w tym przypadku instancja obiektu Action może być przechowywana w sesji lub w żądaniu.
- jeśli żądany URL jest skonfigurowany (w przypadku wysłania formularza do kontrolera), podawana jest nazwa bean odpowiedzialnego za przechowywanie informacji z formularza.
- Dysponując tymi informacjami dostarczonymi przez plik konfiguracyjny, po otrzymaniu żądania URL od klienta kontroler jest w stanie określić, czy należy utworzyć bean i który. Po utworzeniu instancji bean może sprawdzić, czy dane, które zapisał i które pochodzą z formularza, są prawidłowe, czy nie. Metoda bean o nazwie validate jest wywoływana automatycznie przez kontroler. Bean jest tworzony przez programistę. Umieszcza on zatem w metodzie validate kod sprawdzający poprawność danych z formularza. Jeśli dane okażą się nieprawidłowe, kontroler nie będzie kontynuował działania. Przekaże kontrolę do widoku, którego nazwę znajdzie w swoim pliku konfiguracyjnym. Wymiana danych zostaje wówczas zakończona. Należy zauważyć, że programista może zlecić, aby poprawność formularza nie była sprawdzana. Czyni to również w pliku struts-config.html. W takim przypadku kontroler nie wywołuje metody validate fasoli.
- Jeśli dane fasoli są poprawne, nie ma weryfikacji lub nie ma fasoli, kontroler przekazuje kontrolę obiektowi typu Action powiązanemu z plikiem URL. Odbywa się to poprzez wywołanie metody `execute` tego obiektu, do której przekazuje odwołanie do fasoli, którą ewentualnie utworzył. W tym momencie programista wykonuje swoje zadania: może być konieczne wywołanie klas biznesowych lub klas dostępu do danych. Po zakończeniu przetwarzania obiekt `Action` zwraca kontrolerowi nazwę widoku, który ma zostać wysłany w odpowiedzi do klienta.
- Kontroler wysyła tę odpowiedź. Wymiana danych z klientem zostaje zakończona.
Zaczyna się wyłaniać metodologia rozwoju STRUTS:
- definicja widoków. Rozróżnia się widoki będące formularzami od pozostałych.
- Każdy widok formularza generuje definicję w pliku struts-config.xml. Określa się w nim następujące informacje:
- nazwę klasy Bean, która będzie zawierać dane formularza, a także informację, czy dane mają być sprawdzane, czy nie. Jeśli mają być sprawdzane i okażą się nieprawidłowe, należy wskazać widok, który w takim przypadku ma zostać wysłany w odpowiedzi do klienta.
- nazwę klasy Action odpowiedzialnej za przetwarzanie formularza;
- nazwy wszystkich widoków, które mogą zostać wysłane w odpowiedzi do klienta po przetworzeniu żądania. Klasa Action wybierze jeden z nich w zależności od wyniku przetwarzania.
- Każdy widok jest przedmiotem strony JSP. Zobaczymy, że w widokach, a zwłaszcza w widokach formularzy, czasami wykorzystuje się bibliotekę tagów charakterystycznych dla Struts.
- Każdy widok formularza generuje definicję w pliku struts-config.xml. Określa się w nim następujące informacje:
- tworzenie klas JavaBean odpowiadających widokom formularzy
- tworzenie klas Action odpowiedzialnych za przetwarzanie formularzy
- tworzenie ewentualnych klas biznesowych lub klas dostępu do danych
1.4.2. Narzędzia programistyczne STRUTS
Projekt STRUTS jest jednym z projektów Apache Software Foundation. Wiele z tych projektów zgrupowano pod nazwą Jakarta i są one dostępne na stronie http://jakarta.apache.org:

Warto zapoznać się z tą stroną. Wiele z tych projektów jest interesujących dla programistów Java. Jeśli klikniemy powyższy link do Struts, przejdziemy na stronę główną projektu:

Również w tym przypadku zaleca się zapoznanie się z treścią strony głównej. Aby pobrać biblioteki Java projektu Struts, należy kliknąć powyższy link „Binaries”:

W środowisku Windows należy skorzystać z linku 1.1.zip, a w środowisku Unix – z linków 1.1.tar.gz (listopad 2003 r.). Po rozpakowaniu pliku 1.1.zip otrzymujemy następującą strukturę katalogów:

W tej strukturze katalogów znajdują się biblioteki klas Java niezbędne do tworzenia oprogramowania STRUTS. Są one zawarte w plikach .jar lub .war, które działają podobnie jak pliki .zip. Można je otwierać za pomocą tych samych narzędzi. Większość niezbędnych bibliotek znajduje się w powyższym folderze lib:

Oprócz bibliotek klas w formacie .jar znajdują się tam pliki .dtd (Document Type Definition), które zawierają reguły poprawności plików XML. Plik XML może w swojej treści odwoływać się do takiego pliku DTD. Program (zwany parserem), który analizuje zawartość pliku XML, wykorzysta reguły poprawności zawarte w pliku DTD, do którego istnieje odwołanie, w celu ustalenia, czy plik XML jest poprawny pod względem składniowym. Na przykład plik struts-config_1_1.dtd określa reguły tworzenia pliku konfiguracyjnego struts-config.xml dla wersji 1.1 Struts.
Zobaczmy teraz, gdzie należy umieścić poszczególne elementy drzewa katalogów Struts, aby wdrożyć aplikację Struts na serwerze Tomcat.
1.5. Wdrażanie aplikacji Struts
Aplikacja Struts jest aplikacją internetową jak każda inna. Podlega zatem zasadom wdrażania kontenera, w którym jest uruchamiana. W tym przypadku aplikacja, którą nazwiemy strutspersonne, będzie uruchamiana przez serwer Tomcat w wersji 4.x. W załączniku znajduje się instrukcja wdrażania dla serwera Tomcat w wersji 5.x. W tym przypadku postępujemy zgodnie z zasadami wdrażania serwera Tomcat 4.x:
- Definiujemy kontekst „strutspersonne” w pliku konfiguracyjnym Tomcata o nazwie server.xml:
Po wykonaniu tych czynności w razie potrzeby uruchamiamy ponownie serwer Tomcat, aby uwzględnił nowy kontekst. Prawidłowość działania kontekstu możemy sprawdzić, wywołując stronę URL http://localhost:8080/strutspersonne:

Jeśli nie pojawi się strona z błędami, oznacza to, że kontekst jest poprawny.
- W folderze fizycznym powiązanym z kontekstem strutspersonne tworzymy podfolder WEB-INF.
- W folderze WEB-INF aplikacji definiujemy plik konfiguracyjny aplikacji web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
- Klasa kontrolera (servletu) aplikacji to predefiniowana klasa Struts o nazwie ActionServlet. Znajduje się ona w pliku struts.jar. Aby serwer Tomcat mógł znaleźć tę klasę, umieścimy plik struts.jar w folderze <tomcat>\common\lib, który jest jednym z folderów przeszukiwanych przez serwer Tomcat podczas wyszukiwania klas. W rzeczywistości umieścimy tam wszystkie pliki .jar znajdujące się w folderze <struts>\lib, gdzie <struts> jest katalogiem głównym drzewa katalogów Struts.

- Należy również umieścić pliki struts-el.jar i jstl.jar znajdujące się w katalogu <struts>\contrib\struts-el\lib:

- W tym przypadku mamy dostęp do serwera WWW. Nie zawsze tak jest. Jeśli wdrażamy aplikację internetową/Java w kontenerze internetowym, którego nie zarządzamy samodzielnie, najlepiej jest, aby aplikacja zawierała wszystkie potrzebne biblioteki. Należy je wówczas umieścić w folderze WEB-INF/lib, który należy utworzyć.
- Wskazaliśmy, że kontroler potrzebuje pewnej liczby informacji, które zazwyczaj znajduje w pliku struts-config.xml umieszczonym w tym samym folderze co web.xml. W rzeczywistości nazwę tego pliku można dostosować. To parametr config, o którym mowa powyżej, określa tę nazwę.
- Tag <servlet-mapping> wskazuje, że kontroler będzie wywoływany przez wszystkie pliki URL kończące się na rozszerzenie .do. To powiązanie jest wymagane przez Struts. Pliki te zostaną następnie przefiltrowane przez kontroler, który zaakceptuje wyłącznie te pliki, które zostały zadeklarowane w jego pliku konfiguracyjnym
Na razie nasz plik web.xml jest wystarczający.
- Poprosimy aplikację strutspersonne o przesłanie URL /main.do. Zgodnie z poprzednim plikiem web.xml ten plik URL zostanie zatem przekazany do serwletu org.apache.struts.action. Klasa ActionServlet zostanie zainicjowana, a jej metoda init wywołana. Metoda ta próbuje odczytać plik konfiguracyjny określony przez parametr config. Plik ten musi zatem istnieć. Tworzymy następujący plik struts-config.xml:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<action-mappings>
<action
path="/main"
parameter="/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
</struts-config>
Należy zauważyć, że plik DTD z struts-config.xml nie jest taki sam jak ten z pliku web.xml, co wskazuje, że nie mają one tej samej struktury. Dla każdego pliku URL, którym ma zarządzać kontroler, musimy zdefiniować tag <action>. Służy on do wskazania kontrolerowi, co ma zrobić, gdy zostanie poproszony o ten plik URL. W tym przypadku podajemy następujące elementy:
- path="/main": określa nazwę URL skonfigurowanego przez tag <action>. Sufiks .do jest domyślny.
- type="org.apache.struts.actions.ForwardAction": określa nazwę klasy akcji, która ma obsłużyć żądanie. W tym przypadku używamy klasy akcji predefiniowanej w Struts. Nie wykonuje ona żadnych działań samodzielnie, a jedynie przekazuje żądanie klienta do akcji wskazanej w atrybucie parameter.
- parameter="/main.html”: nazwa pliku, do którego ma zostać przekazane żądanie. W tym przypadku jest to statyczny plik.
Podsumowując, gdy użytkownik zażąda pliku URL /main.do, otrzyma plik URL /main.html.
- Plik main.html będzie wyglądał następująco:
<html>
<head>
<title>Application strutspersonne</title>
</head>
<body>
Application strutspersonne active ....
</body>
</html>
Ten plik znajduje się w folderze aplikacji strutspersonne/vues:

Można o to poprosić bezpośrednio, korzystając z kodu URL http://localhost:8080/strutspersonne/main.html:

W tym przypadku kontroler Struts aplikacji nie zadziałał, ponieważ uruchamia się on wyłącznie w przypadku żądania pliku o nazwie *.do. Tutaj jednak wysłano żądanie dotyczące pliku URL /vues/main.html.
- Plik struts-config.xml utworzony wcześniej należy umieścić w tym samym folderze WEB-INF, co plik web.xml:

- Teraz sprawdzimy, czy kontroler aplikacji strutspersonne działa poprawnie, wysyłając żądanie dotyczące plików URL /main.do po ewentualnym ponownym uruchomieniu serwera Tomcat.

W tym przypadku kontroler Struts zadziałał, ponieważ wysłaliśmy żądanie o typie *.do (URL). Otrzymaliśmy oczekiwaną stronę (main.html). Mamy zatem podstawowe elementy działania naszej aplikacji: kontekst strutspersonne, pliki konfiguracyjne web.xml i struts-config.xml oraz biblioteki Struts.
Co by się stało, gdybyśmy zażądali pliku typu /toto.do o nazwie URL? Zgodnie z plikiem web.xml aplikacji strutspersonne, w takiej sytuacji wywoływany jest kontroler Struts w celu przetworzenia tego żądania. Ten z kolei sprawdza swój plik konfiguracyjny struts-config.html i nie znajduje żadnej konfiguracji dla URL /toto. Co więc robi? Spróbujmy:

Otrzymujemy stronę z komunikatami o błędach, co wydaje się normalne. Teraz możemy przystąpić do pisania aplikacji.



