739


Rozdział 10. Programowanie serwerów WWW

Rozdział ten poświęcony jest programowaniu aplikacji serwerów WWW opartych na technologiach CGI/WinCGI oraz ISAPI/NSAPI z wykorzystaniem technologii WebBroker i InternetExpress.

Reprezentantami technologii WebBroker na gruncie C++Buildera są: specjalizowany moduł danych, zwany modułem danych WWW lub krótko modułem WWW (ang. WebModule), bogaty zestaw komponentów i dwa kreatory, z których najczęściej używanym jest Web Server Application Wizard. Drugi z kreatorów - Database Web Application Wizard - umożliwia integrację tabeli danych z modułem WWW.

Wspomniane komponenty podzielone są na dwie grupy. Pierwsza z nich to tzw. komponenty-producenci, m.in.: TPageProducer, TDataSetPageProducer, TDataSetTableProducer, TQueryTableProducer; C++Builder 5 w wersji Enterprise wprowadza dodatkowo dwa komponenty: TMidasPageProducer i TReconcilePageProducer. Komponenty drugiej grupy to podstawowe zręby aplikacji WWW: TWebModule, TWebDispatcher, TWebRequest i TWebResponse.

Przed uruchomieniem przykładowego projektu, ilustrującego zagadnienia poruszane w tym rozdziale, konieczne jest dokonanie pewnych ustawień w środowisku C++Buildera, w szczególności instalacja dodatkowych pakietów i komponentów; szczegółowe instrukcje na ten temat znajdują się w pliku README.TXT umieszczonym na załączonej płycie CD-ROM wraz ze wspomnianym projektem w katalogu WebServer.

Moduły WWW

Określenia „WebBroker” i „Moduł WWW” często używane są zamiennie. W rzeczywistości WebBroker stanowi element modułu WWW (tzw. dyspozytor akcji - action dispatcher) wzbogacający „zwykły” moduł danych (datamodule) o elementy charakterystyczne dla modułu WWW. Innymi słowy, stanowi on centralną część mechanizmów, umożliwiających budowę aplikacji opartych na protokołach ISAPI/NSAPI, CGI lub WinCGI bez troszczenia się o różnice pomiędzy tymi protokołami. Dodatkowo Web Bridge umożliwia wykorzystanie tej samej aplikacji zarówno dla protokołu ISAPI (wszystkie wersje), jak i NSAPI (Netscape do wersji 3.6 włącznie).

Aplikacje serwerów WWW są aplikacjami niewizualnymi - interfejs użytkownika zapewniają im aplikacje klienta. Moduły WWW nie mogą więc zawierać komponentów wizualnych, ich konstruowanie odbywa się poza tym w sposób identyczny z konstruowaniem formularzy.

Web Serwer Application Wizard

Standardowy kreator wspomagający tworzenie aplikacji serwerów WWW - Web Server Application Wizard - dostępny jest na karcie New okna New Items (otwieranego za pomocą opcji File|New menu głównego IDE). Bezpośrednio po jego uruchomieniu wyświetlane jest okno, umożliwiające wybór protokołu tworzonej aplikacji (rys. 10.1):

Rysunek 10.1. Wybór protokołu aplikacji serwera WWW

CGI

Aplikacja typu CGI (Common Gateway Interface) jest aplikacją konsolową, ładowaną przez serwer na żądanie użytkownika i rozładowywaną po zrealizowaniu tego żądania. Żądanie klienta przekazywane jest aplikacji poprzez standardowe wejście (StdIn), natomiast odpowiedź - zazwyczaj w formacie HTML - wypisywana jest do standardowego wyjścia (StdOut).

WinCGI

WinCGI stanowi specyficzną dla Windows implementację protokołu CGI. W przeciwieństwie do standardowego CGI aplikacja WinCGI jest aplikacją typu GUI, chociaż nie manifestuje swej obecności w postaci wizualnej. Rolę standardowych strumieni StdIn i StOut przejął pojedynczy plik INI, za pośrednictwem którego odbywa się wymiana danych pomiędzy serwerem i użytkownikiem.

ISAPI/NSAPI

Rozszerzenia serwera typu ISAPI (Microsoft IIS) i NSAPI (Netscape) tym różnią się od aplikacji CGI/WinCGI, iż w przeciwieństwie do tych ostatnich są bibliotekami DLL. Realizacja żądań odbywa się tu szybciej, ponieważ biblioteki te, ładowane jednorazowo w efekcie pierwszego żądania, pozostają w pamięci serwera.

Jako że przyszłe wersje Netscape z pewnością obsługiwać będą protokół ISAPI, a w chwili obecnej możliwe jest „mapowanie” odwołań do ISAPI na odwołania do NSAPI, w dalszej części rozdziału używać będziemy dla uproszczenia pojedynczej nazwy ISAPI na określenie obydwu protokołów.

CGI czy ISAPI?

Biblioteki DLL, stanowiące rozszerzenia typu ISAPI, mają tę niezaprzeczalną przewagę nad aplikacjami CGI, iż są szybsze i efektywniejsze w działaniu - wszystkie żądania dotyczące danej biblioteki obsługiwane są przez jej pojedynczy egzemplarz, oszczędza się więc zarówno czas, jak i pamięć. Owa „integracja” biblioteki z serwerem ma jednak swe ujemne strony: po pierwsze, jej „podmiana” na nową wersję wymaga zatrzymania serwera i jego ponownego uruchomienia; po drugie i ważniejsze - błędna biblioteka może spowodować załamanie całego serwera, tak więc przed „poważnym” zastosowaniem musi ona zostać przetestowana znacznie gruntowniej niż zwykła aplikacja. Należy zdawać sobie sprawę z faktu, iż biblioteka taka pracuje w warunkach powtarzalnego wykorzystywania (ang. serial reusability), co stwarza okazję do popełnienia wielu typowych błędów, jak np. brak inicjowania zmiennych roboczych przy każdym żądaniu - niezależne aplikacje CGI wolne są od tego problemu.

Niezależnie jednak od tego, czy finalnym produktem danego projektu ma być aplikacja CGI, czy biblioteka ISAPI/NSAPI, jego moduł WWW jest dokładnie taki sam. Stwarza to możliwość przetestowania przyszłej aplikacji CGI w tymczasowej postaci biblioteki DLL (śledzenie bibliotek DLL w C++Builderze jest znacznie łatwiejsze niż śledzenie zewnętrznych procesów). Należy w tym celu utworzyć (w ramach jednej grupy) dwa projekty: zasadniczy, tworzący aplikację CGI, i pomocniczy, tworzący bibliotekę ISAPI. Następnie, używając Menedżera Projektu, należy usunąć moduł WWW z projektu zasadniczego (CGI), a następnie dodać do tegoż projektu moduł WWW z projektu pomocniczego (ISAPI) - w ten sposób obydwa projekty dzielić będą ten sam moduł WWW.

Podstawowe komponenty WebBrokera

Po wybraniu typu aplikacji z okna dialogowego pokazanego na rysunku 10.1 nastąpi utworzenie pustego modułu danych i związanego z nim modułu źródłowego. Używając opcji File|Save All z menu głównego IDE, należy zapisać ów projekt w oddzielnym katalogu (np. WebServer), nadając modułowi nazwę WebMod, zaś projektowi nazwę WebShow (tych właśnie nazw używać będziemy w dalszej części rozdziału).

Przed przystąpieniem do budowy aplikacji należy wpierw zapewnić, by nie była on zależna od żadnych zewnętrznych pakietów. W tym celu należy przejść na kartę Linker opcji projektu (Shift+Ctrl+F11) i wyłączyć opcję Use dynamic RTL, a następnie na kartę Packages i wyłączyć opcję Build with runtime packages.

Moduł WWW jest już gotowy do tego, by umieszczać na nim stosowne komponenty. Komponenty te znajdują się w palecie na stronie Internet (patrz rysunek 10.2) oraz na stronie InternetExpress, którą zajmiemy się nieco później.

Rysunek 10.2. Strona Internet palety komponentów

Spoglądając na rysunek 10.2, widzimy (kolejno od lewej) ikony komponentów TClientSocket i TServerSocket (nie mają one związku z WebBrokerem), a następnie komponenty: TWebDispatcher, TPageProducer, TQueryTableProducer, TDataSetTableProducer, TDataSetPageProducer i TCppWebBrowser - na bazie tego ostatniego zbudowano aplikację, umożliwiającą śledzenie aplikacji WWW z poziomu IDE, którą to aplikację wykorzystywać będziemy w dalszej części rozdziału.

Niezależnie od komponentów znajdujących się w Palecie opiszemy komponenty kluczowe dla modułów WWW: TWebModule, TWebRequest i TWebResponse.

TWebDispatcher

Komponent TWebDispatcher jest tym elementem modułu WWW, który odróżnia ów moduł od „zwykłego” modułu danych (tak więc TDataModule + TWebDispatcher = TWebModule). Jego zadaniem jest przekazywanie żądań użytkowników do odpowiednich komponentów TWebActionItem zgrupowanych w kolekcję reprezentowaną przez właściwość Actions modułu WWW.

W danej aplikacji może wystąpić tylko jeden komponent TWebDispatcher - co oznacza, iż aplikacja ta może posiadać tylko jeden moduł WWW. C++Builder nie zezwoli na umieszczenie dodatkowego komponentu TWebDispatcher w module WWW utworzonym przez kreator - zagadkowy więc wydaje się fakt, iż nie zabrania on tego w stosunku do zwykłego modułu TDataModule.

TWebModule

Moduł WWW, reprezentowany przez komponent TWebModule, może być uważany za „wrażliwą” na Internet odmianę modułu danych (TDataModule). Najważniejszą jego właściwością jest właściwość Actions, wskazująca kolekcję, grupującą komponenty TWebActionItem. Każdy z tych komponentów reprezentuje pewną akcję, stanowiącą odpowiednik żądania użytkownika - ich właściwość PathInfo jest mianowicie tożsama z łańcuchem występującym z treści żądania bezpośrednio po URL; za odnalezienie komponentu TWebActionItem odpowiadającego danemu żądaniu odpowiedzialny jest TWebDispatcher.

Dodawanie i usuwanie komponentów TWebActionItem realizowane jest przez tzw. edytor akcji, stanowiący specjalizowany edytor właściwości Actions. Można go uruchomić na kilka sposobów - na przykład klikając wielokropek w wierszu właściwości Actions lub wybierając opcję Action Editor z menu kontekstowego modułu WWW. W wersji 5 C++Buildera można także posłużyć się projektantem modułu danych (Visual Data Module Designer), w którego drzewie (widocznym w lewej części modułu) przedmiotowe komponenty ukazane są jako potomne w stosunku do właściwości Actions modułu, a ich zestaw zmieniać można za pomocą menu kontekstowego.

Rysunek 10.3 przedstawia uruchomiony edytor akcji, w oknie którego widoczne są pozycje reprezentujące osiem akcji odpowiadających ośmiu różnych żądaniom użytkownika: /hello, /page, /dataset, /table, /query, /login, /browse i /final. Zwróć uwagę, iż pierwszy z komponentów akcji - WebActionItem1 - jest komponentem domyślnym - jego właściwość Default ma wartość true. Jeżeli żądanie użytkownika nie odpowiada właściwości PathInfo w żadnym z komponentów akcji, wybierany jest właśnie komponent domyślny.

Rysunek 10.3. Edytor akcji modułu WWW

W kontekście wybranego przez TWebDispatcher komponentu akcji generowane jest następnie zdarzenie OnAction, w ramach obsługi którego dokonać ma się realizacja żądania użytkownika. Szkielet funkcji obsługującej to zdarzenie wygląda następująco:

void __fastcall TWebModule1::WebModule1WebActionItem1Action(

TObject *Sender, TWebRequest *Request, TWebResponse *Response,

bool &Handled)

{

}

Zanim jednak przystąpimy do oprogramowania poszczególnych akcji, konieczne jest przyjrzenie się pewnym szczegółom związanym z parametrami Request i Response.

TWebResponse

Odpowiedź na żądanie użytkownika, generowana w ramach zdarzenia OnAction, reprezentowana jest przez parametr Response funkcji zdarzeniowej. Parametr ten posiada kilka właściwości, z których najważniejszą jest Content, zawierająca treść odpowiedzi w postaci łańcucha HTML - wykonanie poniższej funkcji spowoduje wygenerowanie odpowiedzi „Hello, world!”:

void __fastcall TWebModule1::WebModule1WebActionItem1Action(

TObject *Sender, TWebRequest *Request, TWebResponse *Response,

bool &Handled)

{

Response->Content = "<H1>Hello, world!</H1>"

}

Format informacji przypisywanej właściwości Content określony jest przez właściwość ContentType. Musi mieć ona postać zgodną z jednym z formatów MIME (w postaci „typ/podtyp”) - wartością domyślną jest text/html. Odpowiedzi w formie binarnej (np. obrazki) nie mogą być jednak przekazywane wprost przez właściwość Content - do ich przekazania służy właściwość ContentStream.

Pozostałe właściwości klasy TWebResponse odpowiedzialne są za kodowanie odpowiedzi (ContendEncoding), zapisywanie cookies w komputerze klienta (Cookies), bezpieczeństwo (StatusCode, ReasonString, WWWAuthenticate, Realm), czas życia dynamicznie generowanego dokumentu (Date, Expires) i informacje diagnostyczne (LogMessage).

TWebRequest

Parametr Request funkcji zdarzeniowej określa różnorodne aspekty żądania użytkownika. Zależnie od metody wysłania żądania (GET lub POST - patrz niżej) określonej przez właściwość Method jego treść dostępna jest albo pod właściwościami Query i QueryFields, albo pod właściwościami Content i ContentFields. Oto prosty przykład wykorzystania tej informacji w generowanej odpowiedzi:

void __fastcall TWebModule1::WebModule1WebActionItem1Action(

TObject *Sender, TWebRequest *Request, TWebResponse *Response,

bool &Handled)

{

Response->Content = "<H1>Hello, world!</H1>";

if (Request->Method == "GET")

Response->Content = Response->Content + "<B>GET</B>" +

"<BR>Query: " + Request->Query;

else

if (Request->Method == "POST")

Response->Content = Response->Content + "<B>POST</B>" +

"<BR>Content: " + Request->Content;

}

Do innych użytecznych właściwości klasy TWebRequest należą m.in.: CookieField (reprezentująca treść cookie odczytanego z komputera klienta), Authorization (odpowiedzialna za bezpieczeństwo) oraz Referrer i UserAgent (związane bezpośrednio z metodą wysyłania żądania).

Wysłanie żądania wg metody GET polega na umieszczeniu jego treści wprost w URL. Jest to metoda względnie szybka, lecz rozmiar przesyłanych danych jest ograniczony do co najwyżej kilku kilobajtów.

W przypadku metody POST żądanie przesyłane jest za pomocą standardowych mechanizmów wejścia-wyjścia (lub przez plik INI w przypadku WinCGI). Przesyłanie jest wolniejsze, lecz rozmiar żądania ograniczony jest tylko wielkością dostępnej przestrzeni dyskowej. Ponadto nie jest widoczna treść przesyłanych danych, nie są więc one narażone na (przypadkowe) uszkodzenie przez użytkownika.

Serwery WWW

Pora przystąpić do przetestowania pierwszej aplikacji serwera WWW. Do działania wymaga ona funkcjonującego serwera WWW, którym może być Personal Web Server (dla Windows 95/98) albo Microsoft Internet Information Server (IIS) dla Windows NT/2000. Firmowy podręcznik użytkownika (Borland C++Builder 5 Developer's Guide), dostarczany wraz z kopią C++Buildera 5, zawiera w rozdziale 30. szczegółowe wskazówki co do skonfigurowania Personal Web Servera tak, by można było na nim testować i śledzić aplikacje CGI i biblioteki ISAPI.

Należy przypomnieć, iż biblioteki ISAPI raz załadowane przez serwer nie są rozładowywane aż do zakończenia jego pracy. Przed utworzeniem nowej wersji biblioteki należy więc zatrzymać serwer (oraz usługę administracyjną IIS), a po zakończeniu kompilacji uruchomić go ponownie (co automatycznie uruchomi również usługę administracyjną IIS). Błędnie skonstruowana biblioteka DLL może ponadto spowodować kompletne załamanie serwera, wymagające jego zatrzymania i ponownego uruchomienia. Problem ten złagodzony został w najnowszej wersji IIS, oferującej opcję Run in Separate Memory Space, powodującą odizolowanie wątpliwego procesu od pozostałych procesów serwera. Możliwe jest także rozładowywanie biblioteki ISAPI po zrealizowaniu każdego żądania i ponowne jej ładowanie po nadejściu kolejnego, co umożliwia jej aktualizację bez zatrzymywania serwera (należy w tym celu wyłączyć opcję Cache ISAPI Application dla danej biblioteki w oknie dialogowym Menedżera IIS); uczynienie tego w „rzeczywistym” serwerze spowoduje jednak utratę korzyści wynikających z szybkości protokołu ISAPI.

W charakterze narzędzia wspomagającego śledzenie aplikacji serwera WWW można użyć freeware'owego programu IntraBob, stworzonego przez jednego z autorów amerykańskiego wydania niniejszej książki. Program ten znajduje się na załączonej płycie CD-ROM w katalogu Software\IntraBob, można go także pobrać ze strony http://www.drbob42.com. Zastępuje on serwer WWW i umożliwia śledzenie bibliotek ISAPI z poziomu IDE C++Buildera lub Delphi. Należy przekopiować jego pliki do katalogu zawierającego przedmiotową bibliotekę ISAPI i użyć pliku IntraBob.exe w charakterze aplikacji nadrzędnej (Host Application), jak pokazuje to rysunek 10.4.

Rysunek 10.4. IntraBob jako aplikacja nadrzędna dla śledzonej biblioteki ISAPI

Śledzenie biblioteki DLL będzie jednakże możliwe tylko wtedy, gdy w jej kodzie źródłowym ustanowiony zostanie punkt przerwania (breakpoint). Ustawmy go więc w jednym z początkowych wierszy funkcji zdarzeniowej komponentu WebActionItem1 (rys. 10.5):

Rysunek 10.5. Ustawienie punktu przerwania w kodzie źródłowym biblioteki DLL

Aby „uruchomić” śledzoną bibliotekę ISAPI, należy wpierw przygotować formularz HTML zawierający odpowiednie żądanie skierowane do tej biblioteki, na przykład:

<HTML>

<BODY>

<H1>WebBroker HTML Form</H1>

<HR>

<FORM ACTION="http://localhost/scripts/WebShow.dll" METHOD=POST>

Name: <INPUT TYPE=EDIT NAME=Name>

<P>

<INPUT TYPE=SUBMIT VALUE=Submit>

</FORM>

</BODY>

</HTML>

Po wyświetleniu tej strony w przeglądarce WWW, wpisaniu czegokolwiek w pole nazwy i kliknięciu przycisku Submit spowodujemy załadowanie biblioteki DLL i uruchomienie aplikacji nadzorującej IntraBob.exe. Przegląda ona formularz HTML i automatycznie wypełnia swe opcje treścią żądania, nazwą modułu wykonywalnego (.EXE lub .DLL), pozostawiając możliwość wpisania odpowiedniej informacji w pole PathInfo w celu spowodowania żądanej akcji modułu WWW. Kartę opcji programu IntraBob przedstawia rysunek 10.6.

Rysunek 10.6. Opcje programu IntraBob

Jeżeli teraz wyślemy do serwera żądanie z pustym polem PathInfo, uruchomi się domyślna akcja, reprezentowana przez komponent WebActionItem1 i wykonanie kodu biblioteki ISAPI zatrzyma się na ustawionym wcześniej punkcie przerwania (rys. 10.7):

Rysunek 10.7. Zatrzymanie wykonywania biblioteki DLL na punkcie przerwania

Zdarza się, iż w czasie wykonywania programu IntraBob sygnalizowany jest następujący wyjątek:

Project Intrabob.exe raised exception class Exception with message
"Only one data module per application". Process Stopped.
Use Step or Run to continue.

Przyczyną tego błędu jest sposób ładowania i rozładowywania przez C++Builder bibliotek ISAPI. Funkcja DllEntryPoint() w głównym pliku *.cpp dokonuje mianowicie bezwarunkowego kreowania egzemplarza modułu danych, niezależnie od przyczyny wywołania określonej przez parametr reason:

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)

{

try

{

Application->Initialize();
Application->CreateForm(__classid(TWebModule1), &WebModule1);
Application->Run();

}

catch (Exception &exception)

{

}

return 1;

}

Próba tworzenia modułu danych podejmowana jest więc nie tylko przy załadowaniu biblioteki (reason == DLL_PROCESS_ATTACH), lecz także przy jej rozładowaniu (reason == DLL_PROCESS_DETACH), jak również podczas przyłączania (reason == DLL_THREAD_ATTACH) i odłączania (reason == DLL_THREAD_DETACH) jej od wątku. Należy więc wyeliminować trzy ostatnie okoliczności, wzbogacając treść funkcji o jedną instrukcję warunkową:

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)

{

try

{

Application->Initialize();

if (reason == DLL_PROCESS_ATTACH)
Application->Cre
ateForm(__classid(TWebModule1), &WebModule1);

Application->Run();

}

catch (Exception &exception)

{

}

return 1;

}

Po ponownym uruchomieniu (F9) zatrzymanej w punkcie przerwania biblioteki otrzymamy stronę stanowiącą odpowiedź na wysłane żądanie (rys. 10.8); zwróć uwagę, iż spacje zamienione zostały na znak „+”, w podobny sposób rozmaite „niedrukowalne” znaki reprezentowane są w postaci kodu szesnastkowego poprzedzonego znakiem „%”.

Rysunek 10.8. Wynikowa strona HTML w oknie programu IntraBob

Komponenty-producenci WebBrokera

Zadaniem komponentów-producentów jest tworzenie wynikowych stron HTML według specyficznych kryteriów. Komponenty te znajdują się na stronie Internet palety komponentów; w kolejnych punktach zaprezentujemy na praktycznych przykładach zastosowanie niektórych z nich.

TPageProducer

Pod parametr Response funkcji zdarzeniowej podstawić można cokolwiek - nawet kompletną stronę WWW - często zdarza się jednak, iż podstawą strony stanowiącej rezultat odpowiedzi powinien być predefiniowany szablon, zaś realizacja żądania użytkownika powinna wówczas polegać na wypełnieniu konkretnymi wartościami pewnych pól tego szablonu. Zadanie to spełnia komponent TPageProducer.

Wspomniany szablon może być dostarczony komponentowi dwojako: albo w postaci pliku dyskowego - nazwa tego pliku jest wówczas reprezentowana przez właściwość HTMLFile - albo w postaci listy łańcuchów TStrings - lista ta wskazywana jest wówczas przez właściwość HTMLDoc. Właściwości HTMLFile i HTMLDoc wykluczają się nawzajem - zmiana jednej z nich powoduje automatyczne „wyczyszczenie” drugiej.

Szablon strony może zawierać dowolny tekst w formacie HTML, jak również specjalne znaczniki rozpoczynające się od znaku „#” (tzw. invalid # tags); znaczniki te są właśnie wspomnianymi polami szablonu i są ignorowane przez przeglądarki WWW, natomiast napotkanie takiego znacznika przez komponent TPageProducer powoduje wygenerowanie zdarzenia OnHTMLTag, którego obsługa powinna zastąpić ów znacznik konkretną zawartością.

W charakterze przykładu rozpatrzmy następujący szablon:

<H1>TPageProducer</H1>

<HR>

<#Greeting> <#Name>

<P>

It's now <#Time> and we're working with a PageProducer

Poniższa funkcja zdarzeniowa zastępuje pole <#Name> imieniem Bob, w pole <#Time> wpisuje aktualną datę i czas, zaś pole <#Greetings> zastępowane jest powitaniem stosownym do pory dnia:

Wydruk 10.1. Przykład zastępowania pól szablonu przez TPageProducer

void __fastcall TWebModule1::PageProducer1HTMLTag(TObject *Sender,

TTag Tag, const AnsiString TagString, TStrings *TagParams,

AnsiString &ReplaceText)

{

if (TagString == "Name")

ReplaceText = "Bob";

else

if (TagString == "Time")

ReplaceText = DateTimeToStr(Now());

else

if ((double)Time() < 0.5) // przed południem

ReplaceText = "Good Morning";

else

if ((double)Time() > 0.7) // po 16:48 (0.7 dnia)

ReplaceText = "Good Evening";

else

ReplaceText = "Good Afternoon";

}

Zastępowanie znacznika ustaloną a priori zawartością jest jednak mało ciekawe, wszak formularze HTML przeważnie pytają użytkownika o imię i nazwisko. Moduł WWW posiada właściwość Request, której wartość identyczna jest z wartością właściwości Request bieżącego komponentu akcji, możemy więc w prosty sposób uzyskać dostęp do poszczególnych pól żądania (za pośrednictwem podwłaściwości QueryFields lub ContentFields) - to samo tyczy się właściwości Response.

Oto więc bardziej rozbudowana obsługa zdarzenia OnHTMLTag:

Wydruk 10.2 Inny przykład zastępowania pól szablonu przez TPageProducer

void __fastcall TWebModule1::PageProducer1HTMLTag(TObject *Sender,

TTag Tag, const AnsiString TagString, TStrings *TagParams,

AnsiString &ReplaceText)

{

if (TagString == "Name")

{

if (Request->Method == "POST")

ReplaceText = Request->ContentFields->Values["Name"];

else

ReplaceText = Request->QueryFields->Values["Name"];

}

else

if (TagString == "Time")

ReplaceText = DateTimeToStr(Now());

else

if ((double)Time() < 0.5)

ReplaceText = "Good Morning";

else

if ((double)Time() > 0.7)

ReplaceText = "Good Evening";

else

ReplaceText = "Good Afternoon";

}

Skojarzenia komponentu-producenta z odpowiednim komponentem akcji dokonuje się za pomocą właściwości Producer tego ostatniego. Jest to nowość wersji 5. C++Buildera, w poprzednich wersjach kojarzenie to musiało być dokonywane jawnie w kodzie programu i polegało na przepisaniu zawartości komponentu-producenta do parametru Response funkcji zdarzeniowej komponentu akcji. W przypadku komponentu wykonującego obsługę żądania /hello naszego projektu wyglądałoby to następująco:

void __fastcall TWebModule1::WebModule1WebActionItem2Action(

TObject *Sender, TWebRequest *Request, TWebResponse *Response,

bool &Handled)

{

Response->Content = PageProducer1->Content();

}

Nowa możliwość wersji 5. eliminuje co prawda konieczność pisania dodatkowego kodu, lecz tym samym uniemożliwia przechwycenie (np. przez ustawienie punktu przerwania) transferu danych od komponentu-producenta do komponentu akcji. W razie potrzeby zawsze można posługiwać się rozwiązaniem tradycyjnym.

Wpisując nazwisko „Bob Swart” do formularza uruchamiającego bibliotekę ISAPI ujrzymy w odpowiedzi stronę prezentowaną na rysunku 10.9; należy przy tym pamiętać o właściwym ustawieniu wartości PathInfo (na /hello) bądź to w oknie programu IntraBob, bądź wprost w treści formularza:

<FORM ACTION="http://localhost/scripts/WebShow.dll/hello" METHOD=POST>

Rysunek 10.9. Strona HTML wyprodukowana przez TPageProducer

Niektóre edytory stron HTML (szczególnie FrontPage) umożliwiają ujmowanie parametrów znaczników w cudzysłowy, nie wchodzące oczywiście w skład parametru (na przykład <#Date "Format">). Cudzysłowy te są konsekwentnie ignorowane przez TPageProducer, o ile jego właściwość StripParamQuotes ustawiona jest na true (jest to ustawienie domyślne).

TDataSetPageProducer

Komponent TDataSetPageProducer jest komponentem pochodnym do TPageProducer. Zastępuje on pole szablonu nie wskazaną bezpośrednio wartością, lecz zawartością pola o wskazanej nazwie w bieżącym rekordzie wskazanego zbioru danych (TDataSet).

Aby zobaczyć to w praktyce, umieść w module WWW komponent TDataSetPageProducer i komponent TTable. Ten ostatni nazwij TableBiolife, pod właściwości DatabaseName i TableName podstaw (odpowiednio) BCDEMOS i biolife.db. Otwórz tabelę, ustawiając na true jej właściwość Active i przypisz tę tabelę do właściwości DataSet komponentu DataSetPageProducer1. Następnie uruchom edytor właściwości HTMLDoc tego ostatniego i utwórz następującą listę łańcuchów:

<H1>BIOLIFE Info</H1>

<HR>

<BR><B>Category:</B> <#Category>

<BR><B>Common_Name:</B> <#Common_Name>

<BR><B>Species Name:</B> <#Species Name>

<BR><B>Notes: </B><#Notes>

Przejdź teraz do czwartego komponentu akcji i przypisz do jego właściwości Producer komponent DataSetPageProducer1. Następnie w formularzu „startowym” zmień rodzaj żądania na /dataset:

<FORM ACTION="http://localhost/scripts/WebShow.dll/dataset" METHOD=POST>

Wynikową stronę HTML przedstawia rysunek 10.10.

Rysunek 10.10. Strona HTML wyprodukowana przez TDataSetPageProducer

Uważny Czytelnik spostrzeże natychmiast, iż na rysunku 10.10 dwie rzeczy są nie w porządku.

Po pierwsze, zamiast zawartości pola Notes widzimy napis (MEMO). Aby zamiast tego napisu ujrzeć zawartość pola, wykorzystamy fakt, iż TDataSetPageProducer, jako pochodny do TPageProducer, generuje zdarzenie OnHTMLTag; w ramach obsługi tego zdarzenia pobierzemy zawartość pola memo i zastąpimy nim wspomniany napis:

void __fastcall TWebModule1::DataSetPageProducer1HTMLTag(TObject *Sender,

TTag Tag, const AnsiString TagString, TStrings *TagParams,

AnsiString &ReplaceText)

{

if (ReplaceText == "(MEMO)")

ReplaceText = TableBiolife->FieldByName(TagString)->AsString;

}

Puste pola memo uwidaczniane są przez TDataSetPageProducer w postaci napisu (Memo), który nie zostanie zastąpiony przez powyższą funkcję pustym łańcuchem - i bardzo dobrze.

Druga anomalią na rysunku 10.10 jest to, iż nie została pobrana zawartość pola Species Name. Przyczyna tego jest prosta - nazwa pola zawiera spację, która przez TPageProducer traktowana jest jako ogranicznik nazwy znacznika, dlatego też TDataSetPageProducer poszukuje pola o nazwie Species, a nie znalazłszy go, produkuje pusty łańcuch.

Problem ten można rozwiązać przez jawne wskazanie, iż znacznik o nazwie Species odnosi się do pola Species Name:

void __fastcall TWebModule1::DataSetPageProducer1HTMLTag(TObject *Sender,

TTag Tag, const AnsiString TagString, TStrings *TagParams,

AnsiString &ReplaceText)

{

if (TagString == "Species") // Species Name

ReplaceText = TableBiolife->FieldByName("Species Name")->AsString;

else

if (ReplaceText == "(MEMO)")

ReplaceText = TableBiolife->FieldByName(TagString)->AsString;

}

Innym rozwiązaniem jest dodanie do struktury tabeli nowego pola obliczanego (wirtualnego), tożsamego z polem Species Name, lecz nie zawierającego spacji w swej nazwie (np. SpeciesName).

Niewątpliwie są to jednak tylko półśrodki, a niniejszy przykład stanowi wyraźny argument na rzecz unikania spacji w nazwach pól bazy danych.

Przykładową - tym razem poprawną - zawartość jednego z rekordów na wynikowej stronie HTML przedstawia rysunek 10.11.

Rysunek 10.11. Poprawiona zawartość strony z rysunku 10.10

Gdybyśmy chcieli zobaczyć zawartość innych rekordów, moglibyśmy przemieszczać się po tabeli bazy danych albo wyświetlać kilka rekordów jednocześnie. Wędrówka po rekordach tabeli wymaga utrzymywania informacji o położeniu bieżącego rekordu, a więc informacji o charakterze pamięci stanu; zajmiemy się tym zagadnieniem w dalszej części rozdziału. Oglądanie kilku rekordów jednocześnie możliwe jest natomiast dzięki kolejnemu komponentowi-producentowi - TDataSetTableProducer.

TDataSetTableProducer

Podobnie jak TDataSetPageProducer, tak i TDataSetTableProducer czerpie wyświetlaną zawartość ze zbioru danych reprezentowanego przez właściwość DataSet. Wyświetlana zawartość obejmuje jednak cały zestaw rekordów i ma postać tabelaryczną (ang. grid).

Umieść w module WWW drugi komponent TTable i nazwij go TableCustomer, a następnie skojarz z bazą danych BCDEMOS i tabelą customer.db, po czym otwórz, ustawiając właściwość Active na true.

Umieść następnie w module WWW komponent TDataSetTableProducer i skojarz go (poprzez właściwość DataSet) z komponentem TableCustomer.

Komponent TDataSetTableProducer posiada szereg właściwości, określający wygląd strony wynikowej - między innymi Header i Footer, określający nagłówek i „stopkę” strony oraz RowAttributes i TableAttributes, definiujące układ (wyrównanie, kolor itp.) wyświetlanych wierszy i tabeli jako całości.

Niewątpliwie jednak lepszy wgląd w wizualną postać tabeli daje właściwość Columns i związany z nią specjalizowany edytor; jego okno z przykładową zawartością przedstawia rysunek 10.12.

Rysunek 10.12. Edytor kolumn komponentu TDataSetTableProducer

Przy pierwszym uruchomieniu edytora ujrzymy wszystkie pola tabeli customer.db. To standardowe zachowanie wszystkich komponentów, pozostających w związku z TDataSet - jeżeli mianowicie nie wybrałeś żadnego pola, przyjmuje się, że chcesz je widzieć wszystkie. Mniej zrozumiały jest natomiast fakt, iż z zestawu tego nie da się usunąć żadnego pola, ani też zmienić kolejności pól.

Otóż aby manipulować zestawem i kolejnością wyświetlanych pól, trzeba wpierw utworzyć ich listę - należy w tym celu kliknąć prawym przyciskiem wyświetlany zestaw pól i z menu kontekstowego wybrać opcję Add All Fields. Na pierwszy rzut oka nic się nie zmieni, jednak poszczególne pola dadzą się teraz usuwać i przemieszczać. Co więcej, właściwości poszczególnych pól dadzą się niezależnie ustawiać za pomocą inspektora obiektów i to w sposób automatycznie odzwierciedlany w edytorze kolumn.

Podobnie jak w przypadku poprzednich komponentów-producentów, skojarzymy teraz komponent TDataSetTableProducer z odpowiednim - tym razem piątym - komponentem akcji, oczywiście za pomocą właściwości Producer.

Godnym uznania jest fakt, iż TDataSetTableProducer umożliwia formatowanie wyświetlanego widoku na poziomie poszczególnych komórek - przed wyświetleniem każdej komórki generowane jest mianowicie zdarzenie OnFormatCell, dające okazję zmiany jej standardowego wyglądu. Zilustrujemy tę możliwość, wyświetlając na czerwonym tle (w kolumnie Country) pozycje związane ze Stanami Zjednoczonymi i nadając pustym komórkom kolor srebrny:

void __fastcall TWebModule1::DataSetTableProducer1FormatCell(

TObject *Sender, int CellRow, int CellColumn, THTMLBgColor &BgColor,

THTMLAlign &Align, THTMLVAlign &VAlign, AnsiString &CustomAttrs,

AnsiString &CellData)

{

if (CellData == "") BgColor = "Silver";

else

if ((CellColumn == 6) && (CellData.Pos("US") > 0))

BgColor = "Red";

}

Uzyskany wygląd wyświetlanej tabeli przedstawia rysunek 10.13.

Rysunek 10.13. Tabela bazy danych wyświetlana za pomocą komponentu TDataSetTableProducer

TQueryTableProducer

Nazwa komponentu TQueryTableProducer bierze się bynajmniej nie stąd, iż można z nim skojarzyć komponent TQuery - można go bowiem skojarzyć z dowolnym producentem posiadającym właściwość DataSet - lecz stąd, iż posiada on dodatkowe elementy obsługi parametryzowanych zapytań SQL.

Umieść w module WWW komponenty TQueryTableProducer i TQuery; drugi z nich nazwij QueryDemos, skojarz z bazą BCDEMOS i przypisz następujący tekst właściwości SQL:

SELECT * FROM ORDERS.DB AS O

WHERE (O.CustNo = :CustNo)

Zapytanie to, jak widać, posiada jeden parametr - CustNo. Konieczne jest określenie jego typu - w tym celu należy w inspektorze obiektów uruchomić edytor właściwości Params, podświetlić jedyny parametr w wyświetlonej liście i ustawić jego typ (DataType) jako ftInteger, charakter (ParamType) jako ptInput i domyślną wartość (Value) jako 0.

W końcu należy „otworzyć” komponent QueryDemos i skojarzyć go z producentem TQueryTableProducer za pośrednictwem właściwości Query tego ostatniego.

Możliwości komponentu TQueryTableProducer w zakresie wyświetlania (i jego konfiguracji) są takie same jak w przypadku TDataSetTableProducer - obydwa wszak wywodzą się z klasy TDSTableProducer, przy czym komponent TQueryTableProducer wprowadza dodatkową właściwość Query.

Komponent TQueryTableProducer poszukuje nazwy parametru (w tym przypadku CustNo) wśród nazw pól składających się na właściwość ContentFields (lub QueryFields, zależnie od metody wysłania - GET albo PUT) i w przypadku znalezienia pola o żądanej nazwie podstawia jego wartość pod parametr w treści zapytania SQL. Zdefiniujmy w charakterze przykładu następujący formularz:

<HTML>

<BODY>

<H1>WebBroker HTML Form</H1>

<HR>

<FORM ACTION="http://localhost/scripts/WebShow.dll/query" METHOD=POST>

CustNo: <INPUT TYPE=EDIT NAME=Name>

<P>

<INPUT TYPE=SUBMIT VALUE=Submit>

</FORM>

</BODY>

</HTML>

Zwróć uwagę, iż nazwa pola pobieranego z formularza tożsama jest z nazwą parametru zapytania SQL. Wpisując w to pole np. 1221, otrzymamy listę wszystkich zamówień związanych z kontrahentem o numerze 1221 - w granicach określonych przez właściwość MaxRows, zawierającą ograniczenie na liczbę wyświetlanych wierszy. Nadając tej właściwości dużą wartość, zapewniamy sobie co prawda wyświetlenie wszystkich interesujących rekordów, lecz ryzykujemy długie oczekiwanie na moment, gdy cokolwiek pojawi się w oknie przeglądarki - wyświetlanie tabeli nie zostanie bowiem rozpoczęte przed odczytaniem znacznika </TABLE>. Uwaga ta odnosi się także do komponentu TDataSetTableProducer.

Rysunek 10.14 przedstawia wyświetlenie wyników zapytania w sytuacji, gdy w pole Custno na formularzu wpisano 1221.

Rysunek 10.14. Wynikowy zbiór parametryzowanego zapytania SQL wyświetlany za pomocą komponentu TQueryTableProducer

Uzyskany wynik stwarza doskonałą okazję, by połączyć go z tym pokazanym na rysunku 10.13 tak, by dla wskazanego kontrahenta wyświetlone zostały (w osobnym oknie) związane z nim zamówienia. Wymaga to modyfikacji funkcji zdarzeniowej formatującej komórki tak, by komórki w kolumnie CustNo zawierały hiperłącza, których klikanie powodować będzie generowanie nowych żądań /query. Można także przekształcić każdą ze wspomnianych komórek w formularz z ukrytym polem o nazwie Custno i predefiniowaną zawartością równą numerowi kontrahenta. Te dwie możliwości - hiperłącza i formularze - rozróżniane są w treści funkcji zdarzeniowej za pomocą symbolu kompilacji warunkowej LINK.

Oto zapowiadana funkcja w swej ostatecznej postaci:

void __fastcall TWebModule1::DataSetTableProducer1FormatCell(

TObject *Sender, int CellRow, int CellColumn, THTMLBgColor &BgColor,

THTMLAlign &Align, THTMLVAlign &VAlign, AnsiString &CustomAttrs,

AnsiString &CellData)

{

if ((CellColumn == 0) && (CellRow > 0)) // 1.kolumna - CustNo

CellData =

#ifdef LINK

// hiperłącze

"<A HREF=\"http://localhost/scripts/WebShow.dll/query?CustNo=" +

CellData + "\">" + CellData + "</A>";

#else

// formularz

(AnsiString)"<FORM ACTION=\"WebShow.dll/query\" METHOD=POST>" +

"<INPUT TYPE=HIDDEN NAME=CustNo VALUE=" + CellData + ">" +

"<INPUT TYPE=SUBMIT VALUE=" + CellData + ">" +

"</FORM>";

#endif

else

if (CellData == "") BgColor = "Silver";

else

if ((CellColumn == 6) && (CellData.Pos("US") > 0))

BgColor = "Red";

}

Na rysunku 10.15 widzimy znajomą kartotekę kontrahentów ze zmodyfikowaną kolumną CustNo, rysunek 10.16 przedstawia natomiast listę zamówień wybranego kontrahenta.

Rysunek 10.15. Wyświetlenie kartoteki kontrahentów za pomocą komponentu TDataSetTableProducer, z hiperłączami w pierwszej kolumnie

Rysunek 10.16, Wyświetlenie zamówień związanych z określonym kontrahentem, za pomocą komponentu TQueryTableProducer

Jako że IntraBob potrafi śledzić jedynie skutki żądań generowanych przez formularze HTML, nie będzie on w stanie śledzić skutków kliknięć hiperłącza kontrahentów - za to bez problemów poradzi sobie z komórkami-formularzami.

Zarządzanie stanem sesji

Pamiętasz wynik produkowany przez TDataSetTableProducer? Wspominaliśmy wówczas o możliwości utrzymywania informacji o numerze bieżącego rekordu - jako przykładowej informacji o stanie dialogu użytkownika z serwerem. Sprawa ta jest o tyle interesująca, iż HTTP jako protokół bezstanowy nie oferuje żadnych standardowych środków w tym zakresie, muszą więc one być zapewnione za pomocą odrębnych technik.

„Gruby URL”

Najprostszym sposobem przechowania informacji o stanie jest uczynienie jej częścią URL - zrobiliśmy to już w przypadku hiperłączy w wyświetlanej kartotece kontrahentów:

"<A HREF=\"http://localhost/scripts/WebShow.dll/query?CustNo=" +

CellData + "\">" + CellData + "</A>";

gdzie uczyniliśmy numer kontrahenta częścią URL. Na tej samej zasadzie, jeżeli chcielibyśmy przeglądnąć pierwszy rekord tabeli, można by sformułować żądanie jako:

<FORM ACTION="http://localhost/scripts/WebShow.dll/dataset?RecNo=1"

METHOD=POST>

Mimo iż generalna metoda przesyłania danych określona jest jako POST, dane przesyłane są zgodnie z konwencją GET - w sposób widoczny dla użytkownika, a więc podatny na przypadkowe zmiany. Z tego względu metoda ta nie wydaje się godną polecenia.

Cookies

Cookies stanowią porcje danych zapisywane przez serwer na komputerze klienta - i jakkolwiek cała inicjatywa wychodzi od serwera, użytkownik może odrzucać cookies i ogólnie blokować ich przyjmowanie.

Cookie wysyłane jest jako część odpowiedzi Response, za pomocą metody SetCookieField(). Stanowi ono listę łańcuchów (TStringList) o postaci „nazwa=wartość” każdy, tak więc scenariusz wysłania przykładowego cookie mógłby wyglądać na przykład tak:

TStringList* Cookies = new TStringList();

Cookies->Add("RecNo="+IntToStr(Master->RecNo));

Response->SetCookieField(Cookies, NULL, NULL, Now()+1, false);

Cookies->Free();

Drugi i trzeci parametr metody SetCookieField() określają docelową domenę i ścieżkę dla cookie - parametry te mogą zostać „puste”. Czwarty parametr określa datę ważności cookie (tu: do następnego dnia), zaś ostatni parametr decyduje o tym, czy przesłanie cookie ma się odbywać z użyciem bezpiecznego połączenia.

Zapisanie cookie w komputerze użytkownika to jednak dopiero połowa pracy; należy je jeszcze odczytać, gdy okaże się potrzebne, i zrobić użytek z zawartej w nim informacji. Poszczególne łańcuchy cookie reprezentowane są przez poszczególne pola właściwości Request->CookieFields, tak więc odczytanie numeru ostatnio przeglądanego rekordu może być wykonane w następujący sposób:

int RecNo = StrToInt(Request->CookieFields->Values["RecNo"]);

Ukryte pola formularzy HTML

Aby zrealizować nawigowanie po rekordach bazy danych, należy utworzyć nowy formularz z czterema przyciskami oznaczającymi (odpowiednio): pierwszy, poprzedni, następny i ostatni rekord. Jednocześnie w formularzu tym należy umieścić dodatkowy znacznik <#RecNo>:

<FORM ACTION="http://localhost/scripts/WebShow.dll/dataset" METHOD=POST>

<H1>BIOLIFE Info</H1><HR>

<INPUT TYPE=SUBMIT NAME=SUBMIT VALUE="First">

<INPUT TYPE=SUBMIT NAME=SUBMIT VALUE="Prior">

<INPUT TYPE=SUBMIT NAME=SUBMIT VALUE="Next">

<INPUT TYPE=SUBMIT NAME=SUBMIT VALUE="Last">

<#RecNo>

<BR><B>Category:</B> <#Category>

<BR><B>Common_Name:</B> <#Common_Name>

<BR><B>Species Name:</B> <#Species Name>

<BR><B>Notes: </B><#Notes>

Aby zastąpić znacznik <#RecNo> numerem bieżącego rekordu, należy użyć następującej składni:

<INPUT TYPE=HIDDEN NAME=RecNo VALUE=1>

Powyższe polecenie nadaje polu wartość 1. Ukryte pola formularzy nie są widoczne dla użytkownika, ich zawartość przesyłana jest jednak z powrotem do serwera i do modułu WWW aplikacji, gdy użytkownik naciśnie jeden z przycisków SUBMIT.

Aby zrealizować całą tę historię, należy ponownie zmodyfikować obsługę zdarzenia OnHTMLTag, implementując dodatkowo wyświetlanie numeru bieżącego rekordu:

Wydruk 10.4. Wyświetlanie numeru bieżącego rekordu przez TDataSetPageProducer

void __fastcall TWebModule1::DataSetPageProducer1HTMLTag(TObject *Sender,

TTag Tag, const AnsiString TagString, TStrings *TagParams,

AnsiString &ReplaceText)

{

if (TagString == "RecNo")

ReplaceText =

"<INPUT TYPE=HIDDEN NAME=RecNo VALUE=" +

IntToStr(TableBiolife->RecNo) + // numer bieżącego rekordu

"> " + IntToStr(TableBiolife->RecNo) +"/"+

IntToStr(TableBiolife->RecordCount) + "<P>";

else

if (TagString == "Species") // Species Name

ReplaceText = TableBiolife->FieldByName("Species Name")->AsString;

else

if (ReplaceText == "(MEMO)")

ReplaceText = TableBiolife->FieldByName(TagString)->AsString;

}

Należy teraz „oprogramować” każdy z czterech przycisków (First, Prior, Next i Last) :

Wydruk 10.5. Zmiana bieżącego rekordu

void __fastcall TWebModule1::WebModule1WebActionItem1Action(

TObject *Sender, TWebRequest *Request, TWebResponse *Response,

bool &Handled)

{

DataSetPageProducer1->DataSet->Open();

// na wszelki wypadek, gdyby nie był jeszcze otwarty

int RecNr = 0;

// pobranie numeru aktualnego rekordu

AnsiString Str = Request->ContentFields->Values["RecNo"];

if (Str != "") RecNr = StrToInt(Str);

// rozpoznanie naciśniętego przycisku

Str = Request->ContentFields->Values["SUBMIT"];

if (Str == "First") RecNr = 1; // pierwszy rekord

else

if (Str == "Prior") RecNr--; // poprzedni rekord

else

if (Str == "Last") RecNr = // ostatni rekord

DataSetPageProducer1->DataSet->RecordCount;

else // nastepny rekord

RecNr++;

// zabezpieczenie przed wyjściem poza koniec zbioru

if (RecNr > DataSetPageProducer1->DataSet->RecordCount)

RecNr = DataSetPageProducer1->DataSet->RecordCount;

// zabezpieczenie przed wyjściem przed początek zbioru

if (RecNr < 1) RecNr = 1;

// czy faktycznie zmieniono numer rekordu?

if (RecNr != DataSetPageProducer1->DataSet->RecNo)

DataSetPageProducer1->DataSet->MoveBy(

RecNr - DataSetPageProducer1->DataSet->RecNo);

Response->Content = DataSetPageProducer1->Content();

}

„Unowocześniony” wygląd przeglądarki przedstawia rysunek 10.17 - różni się on od tego z rysunku 10.11 zestawem przycisków i informacją o liczbie rekordów oraz numerze rekordu bieżącego.

Rysunek 10.17. Strona HTML utworzona przez TDataSetPageProducer, z możliwością nawigowania po tabeli rekordów

W naszym przykładzie rekordy tabeli identyfikowane były dla uproszczenia numerami bezwzględnymi; przedstawioną koncepcję można uogólnić na identyfikowanie rekordów za pomocą unikatowych kluczy, co okazałoby się znacznie praktyczniejsze w przypadku dużych tabel podlegających intensywnym zmianom.

Bezpieczeństwo aplikacji sieciowych

Problem bezpieczeństwa aplikacji WWW nabiera coraz większego znaczenia wobec gwałtownego rozwoju komercyjnych zastosowań Internetu z jednej strony, a coraz wymyślniejszych atakow hakerskich z drugiej. Nawet więc w przypadku najprostszej aplikacji WWW warto całkiem serio pomyśleć o bezpieczeństwie przetwarzanych przez nią danych.

Na szczęście opracowano kilka skutecznych technik, mających na celu ochronę przetwarzania rozproszonego. Przede wszystkim należy dążyć do rzeczywistego rozproszenia danych - nie należy więc na przykład umieszczać bazy danych na serwerze WWW, lecz np. w odrębnym serwerze plików, oddzielonym od serwera WWW firewallem; wskazane jest również umieszczenie drugiego firewalla pomiędzy serwerem WWW a Internetem. Jeżeli nawet w tych warunkach niepowołanej osobie uda się dostać do serwera WWW (co już wymaga pokonania przynajmniej jednego firewalla), nie oznacza to jeszcze dostępu do użytecznych danych. Serwer WWW staje się tym samym czymś w rodzaju „strefy zdemilitaryzowanej” oddzielonej szeregiem firewalli i proxy zarówno od świata zewnętrznego, jak i od przetwarzanych danych.

C++Builder 5 wraz z nowym komponentem TWebConnection udostępnia obsługę serwerów proxy. Komponent ten używany jest po stronie klienta w wielowarstwowych aplikacjach MIDAS (Multitier Distributed Application Services) i umożliwia aplikacji-klientowi połączenie się z odległą bazą danych (w wyższych warstwach modelu) za pośrednictwem proxy HTTP. Po stronie serwera WWW odpowiedzialność za połączenie z bazą danych spoczywa wówczas na bibliotece ISAPI o nazwie httpsvr.dll, która musi być uprzednio zainstalowana. Za pomocą właściwości Proxy komponentu TWebConnection możliwe jest podanie adresu IP serwera proxy, możliwe jest także określenie nazwy użytkownika (właściwość UserName) i jego hasła (właściwość Password); sam komponent TWebConnection nie zajmuje się przy tym weryfikacją nazwy i hasła użytkownika, lecz pełni rolę swoistego „routera”, przekazującego te informacje do serwera proxy. Możliwa jest rezygnacja z serwera proxy, należy wówczas pozostawić niewypełnione właściwości: Proxy, UserName i Password, możliwe jest także wykorzystanie połączeń „wysokopoziomowych”, jak TDCOMConnection czy TCORBAConnection zamiast „zwykłych” połączeń HTTP.

SSL - warstwa bezpiecznych gniazd

Niezależnie od różnych technik zabezpieczania dostępu do danych możliwe jest także zabezpieczenie samych danych na wypadek przechwycenia ich przez niepowołanego odbiorcę za pomocą np. podsłuchu - co nabiera szczególnego znaczenia w przypadku danych o wysokim stopniu tajności, jak np. numer karty kredytowej. Powszechnie wykorzystywanym zabezpieczeniem tego rodzaju jest szyfrowanie danych w oparciu o tzw. protokół bezpiecznych gniazd (ang. SSL - Secure Socket Layer) i przekazywanie ich w tej postaci w ramach (bezpiecznego) połączenia pomiędzy klientem a serwerem. Przeglądarki WWW sygnalizują fakt bezpiecznego połączenia za pomocą różnych odmian zamkniętej kłódki lub zamka, zaś prefiks adresu ma postać https:// (ang. S-HTTP - Secure HTTP) zamiast standardowego http://.

Funkcjonowanie protokołu SSL opiera się na wykorzystaniu dwóch powiązanych ze sobą kluczy - publicznego i prywatnego, uzyskiwanych przez właściciela serwera z zaufanego źródła, np. VeriSign. Bezpieczne połączenie rozpoczyna się od przesłania klientowi przez serwer klucza publicznego. Klient następnie szyfruje podlegające przesłaniu dane za pomocą tego klucza i w takiej postaci wysyła je do serwera. Ewentualne przechwycenie zarówno klucza publicznego, jak i zaszyfrowanych danych nie stanowi żądnego zagrożenia, dane te mogą być bowiem rozszyfrowane jedynie przy użyciu klucza prywatnego - ten zaś znany jest tylko serwerowi, a algorytm szyfrowania wyklucza uzyskanie go na podstawie klucza publicznego w rozsądnym czasie. Szyfrowanie i deszyfracja danych jest immanentną cechą samego połączenia i jako takie jest ono „przezroczyste” dla aplikacji serwera WWW.

Autoryzacja

Oprócz szyfrowania przesyłanych danych celowe wydaje się także reglamentowanie (w zróżnicowanym stopniu) dostępu do wybranych stron WWW. Wymaga to wykorzystania pewnych technik autoryzacji dostępu - zazwyczaj prostej weryfikacji nazwy i hasła użytkownika, która, nie mając bynajmniej krytycznego znaczenia, okazuje się całkowicie wystarczająca, przynajmniej na użytek przykładów prezentowanych w niniejszym rozdziale.

Jakkolwiek serwer IIS umożliwia przypisanie uprawnień do poszczególnych katalogów, znacznie bardziej atrakcyjną - i czasochłonną dla programistów - jest dynamiczna weryfikacja danych użytkownika.

Nagłówki HTTP

Informacja wynikowa produkowana przez aplikację CGI lub bibliotekę ISAPI rozpoczyna się nagłówkiem HTTP, po którym następuje pusty wiersz i „właściwa” zawartość. Integralną częścią nagłówka jest typ przesyłanej informacji - na przykład text/html czy image/gif - określający, jak następująca dalej informacja ma być interpretowana przez aplikację-klienta HTTP. Serwer WWW z własnej inicjatywy dodaje także - jako pierwszy wiersz - specjalną informację o statusie tworzonej dynamicznie strony WWW, zazwyczaj w postaci

HTTP/1.0 200 OK

oznaczającej, iż w dalszym ciągu należy spodziewać się poprawnych informacji.

Załóżmy teraz, iż informacja ta podmieniona zostanie na inną, oznaczającą np. nieautoryzowany dostęp i wymagającą logowania do dziedziny /DrBob:

HTTP/1.0 401 Unauthorized

content-type: text/html

WWW-Authenticate: Basic realm="/DrBob"

Parametr Request funkcji obsługującej zdarzenie OnAction komponentu akcji posiada właściwość Authorization, zawierającą zakodowaną informację o autoryzacji logującego się użytkownika. Weryfikując tę informację, mamy możliwość odpowiedniego ustawienia kodu błędu i werbalnego opisu błędnej sytuacji za pomocą właściwości (odpowiednio) StatusCode i ReasonString parametru Response:

Wydruk 10.6. Autoryzacja żądania aplikacji-klienta WWW

void __fastcall TWebModule1::WebModule1WebActionItem7Action(

TObject *Sender, TWebRequest *Request, TWebResponse *Response,

bool &Handled)

{

AnsiString Auth = Request->Authorization;

if (Auth.Pos("Basic ") == 1) Auth.Delete(1,6);

Auth = UnBase64(Auth);

if (Auth.Pos("bswart") == 0)

{

Response->StatusCode = 401;

Response->ReasonString = "Unauthorized";

Response->WWWAuthenticate = "Basic";

Response->Realm = "/DrBob";

Response->SendResponse();

}

else

{

Response->Content = "Welcome: ["+Request->Authorization+"]=["+Auth+"])";

}

}

Bezpośrednio po wyprodukowaniu odpowiedzi Response jest ona bezzwłocznie wysyłana do przeglądarki klienta za pomocą metody SendResponse() - nie jest to konieczne, lecz pozwala uniknąć pewnej zwłoki czasowej.

Gdybyśmy powyższy przykład zastosowali w konsolowej aplikacji CGI, ujrzelibyśmy w efekcie pustą stronę - serwer WWW w dalszym ciągu produkowałby bowiem standardową informację HTTP/1.0 200 OK. w pierwszym wierszu, zaś kolejne informacje na temat statusu strony są po prostu ignorowane. Aby zmusić serwer WWW do rezygnacji z dołączania tego wiersza, należy zmienić nazwę pliku wykonywalnego CGI lub biblioteki ISAPI w taki sposób, by nazwa ta rozpoczynała się ciągiem znaków „NPH-” (Non-Protocol Header) - na przykład na NPH-WebShow.dll.

Problem z biblioteką VCL

Kod przedstawiony na wydruku 10.6 funkcjonuje bez zarzutu w przeglądarce Netscape Navigator, stwarza jednak pewien problem w przypadku Internet Explorera w wersji 5 i wyższych, a to z powodu zapamiętywania raz wprowadzonego hasła - rzecz zupełnie niedopuszczalna w sytuacji, gdy z jednego komputera korzysta więcej osób o być może sprzecznych interesach.

Kolejny kłopot polega na tym, iż ani Netscape Navigator, ani Internet Explorer nie umożliwiają wyświetlenia nazwy dziedziny (realm) w dialogu logowania, nazwa ta musi więc być jawnie przypisana właściwości Realm parametru Response - niestety, właściwość ta jest zupełnie ignorowana przez metodę SendResponse().

W treści biblioteki VCL - konkretnie w plikach IsapiAPP.pas i CgiApp.pas - znajduje się bowiem błąd, związany z implementacją metody SendResponse() klas TCGIResponse i TISAPIResponse: otóż instrukcja:

AddHeaderItem(WWWAuthenticate, 'WWW-Authenticate %s'#13#10);

powinna zostać zastąpiona następującą sekwencją:

Tmp := Format('WWW-Authenticate: %s realm="%s"'#13#10, ['%s', Realm]);

AddHeaderItem(WWWAuthenticate, Tmp);

(należy oczywiście zadeklarować lokalną zmienną Tmp typu String). Wskazane jest przekopiowanie wspomnianych plików do katalogu zawierającego projekt, dołączenie ich do projektu (za pomocą opcji Project|Add to Project IDE C++Buildera), dokonanie wspomnianych zmian i przekompilowanie projektu w trybie zupełnym (Build).

Zabezpieczanie aplikacji WWW

Łatwość tworzenia aplikacji WWW za pomocą narzędzi typu RAD oraz elastyczność, z jaką aplikacje te zdolne są operować danymi, jest naturalną konsekwencją gwałtownie rosnących zastosowań Internetu w różnorodnych komercyjnych dziedzinach ludzkiej działalności - czego przykładem są sklepy internetowe, aukcje online, internetowy dostęp do kont bankowych itp. Wraz z rozwojem komercyjnych usług internetowych zwiększa się także rozmiar przesyłanych i przechowywanych danych.

C++Builder nie jest bynajmniej jedynym, ani też najbardziej popularnym narzędziem tworzenia aplikacji WWW; z najbardziej znanych konkurentów wymienić można chociażby ASP czy Perla. Generalnie każde narzędzie ma swoje dobre i złe strony, jednak to nie wybór konkretnego narzędzia jest najważniejszą decyzją projektową, lecz metoda bezpiecznego przechowywania danych generowanych przez tworzone aplikacje WWW.

Wspomniany wcześniej protokół SSL ogranicza się jedynie do bezpiecznego przesyłania danych pomiędzy klientem a serwerem; zabezpieczenie dostępu do przetworzonych danych jest już sprawą samej aplikacji. Niestety, ten aspekt zagadnienia bywa często niedoceniany lub kompletnie ignorowany, czego konsekwencją bywa utrata znacznej nieraz ilości danych czy po prostu kradzież informacji poufnych - przykład kradzieży ponad 300 tysięcy numerów kart kredytowych z sieci CD Universe w 1999 roku świadczy o tym, z jak wielkiej wagi zagadnieniem mamy tu do czynienia.

Solidność zabezpieczeń

Zagadnienie bezpieczeństwa danych przetwarzanych przez aplikacje sieciowe zrodziło się w konsekwencji pogodzenia dwóch sprzecznych wymagań: z jednej strony dostęp do poufnych danych powinien być jak najbardziej ograniczony, z drugiej strony udostępnianie czegokolwiek za pośrednictwem Internetu z definicji oznacza wystawienie tego czegoś na dostęp nieograniczony. Niezależnie od coraz to lepszych technik zabezpieczania danych powstają jednocześnie coraz bardziej wymyślne metody łamania tychże zabezpieczeń. Jedyne, czego tak naprawdę można być pewnym, to to, iż haker jest cierpliwy i wykorzysta każdą słabą stronę zabezpieczeń, by w końcu dostać się (bezprawnie) do upragnionych danych. Rysunek 10.18 stanowi jeden z dowodów na to, jak złudne jest poczucie bezpieczeństwa na podstawie haseł przechowywanych w programie czy danych w jawnej postaci - przedstawia on aplikację typu ISAPI, będącą de facto binarnym edytorem danych, umożliwiającą wgląd w znakową i szesnastkową reprezentację badanego obszaru. Także algorytm dynamicznego tworzenia haseł lub konkatenacji różnych łańcuchów może być często łatwo rozszyfrowany za pomocą różnego rodzaju dekompilatorów i disasemblerów.

Rysunek 10.18. Hackman hexadecimal editor, copyright 1996 - 1999 by TechnoLogismiki

Kryptografia to jest to

Nie będziemy się tutaj zagłębiać w teoretyczne podstawy kryptografii, skoncentrujemy się raczej na jej zastosowaniu do aplikacji WWW. Co prawda fundamenty matematyczne leżące u podstaw technik kryptograficznych bywają ogromnie skomplikowane, lecz użytkownik stosujący kryptografię do zabezpieczania danych niekoniecznie musi być ich świadom.

Ogólnie pojmowana kryptografia ma różnorodne oblicza i często rozmaite działania, będące istotnie szyfrowaniem danych, okazują się w efekcie nieskuteczne, a niekiedy wręcz nieprawidłowe. To bardzo niedobry znak, bowiem łańcuch rozmaitych zabezpieczeń jest tak silny, jak jego najsłabsze ogniwo. Projektanci stosujący rozmaite, ich zdaniem bardzo silne, techniki zabezpieczania danych często zapominają o tym, iż uczestniczące w tym szyfrowaniu wzorce danych przechowywane są wraz z tymi danymi, a algorytmy szyfrujące mogą zostać rozpoznane przez hakera, dysponującego egzemplarzem aplikacji.

Należy zdawać sobie sprawę z faktu, iż tak naprawdę nie istnieją zabezpieczenia idealne i żadna metoda szyfrowania nie uczyni danych absolutnie bezpiecznymi. Postęp w wynajdywaniu nowych technik łamania zabezpieczeń, luki w samych zabezpieczeniach i determinacja hakerów powodują, iż każda poufna informacja prędzej czy później może stać się ich łupem.

Protokoły, algorytmy i funkcje mieszające

Protokół składa się z szeregu procedur lub etapów, z których niektóre stosować mogą szyfrowanie danych. Niedopracowanie którejś z tych procedur czy jakakolwiek inna luka w implementacji protokołu mogą sprawić, iż najbardziej wymyślny algorytm szyfrowania okaże się bezskuteczny.

Algorytm szyfrowania jest w swej istocie funkcją matematyczną przekształcającą dane do lub z postaci zaszyfrowanej. Z punktu widzenia stosowanych do szyfrowania kluczy algorytmy podzielić można na symetryczne - czyli stosujące ten sam klucz do szyfrowania i deszyfrowania - oraz asymetryczne, posługujące się w tym celu dwoma różnymi kluczami: publicznym do szyfrowania danych i prywatnym do ich odszyfrowywania. Przykładem algorytmów symetrycznych są DES (ang. Data Encryption Standard), Blowfish, Twofish i RC2. Do algorytmów asymetrycznych należą między innymi: RSA (od nazwisk wynalazców: Rivesta, Shamira i Adlemana), algorytm Rabina i algorytm plecakowy (Knapsack algorithm). Biorąc pod uwagę stopień komplikacji wymienionych algorytmów, łatwo zrozumieć, iż próby ich złamania muszą być pracochłonne, jakkolwiek każde niedopatrzenie projektantów może tę pracochłonność drastycznie obniżyć.

Funkcje mieszające (hashes) otrzymują łańcuchy zmiennej długości i produkują na ich podstawie łańcuchy stałej długości (ang. digests). Dla wielu takich funkcji łatwo jest odgadnąć funkcję odwrotną, czyli odtworzyć łańcuch oryginalny na podstawie jego zaszyfrowanej postaci, wyjątkiem w tym względzie są jednak tzw. jednokierunkowe funkcje mieszające (ang. one-way hashes), gdzie zmiana jednego bitu w łańcuchu oryginalnym skutkuje drastyczną zmianą w jego zaszyfrowanej postaci. Funkcje takie bywają wykorzystywane do generowania unikatowych kluczy sesji dla algorytmów symetrycznych. Przykładami algorytmów szyfrujących, wykorzystujących jednokierunkowe funkcje mieszające, są: SHA, MD4 i MD5.

Przykład - bezpieczny sklep internetowy

W charakterze przykładu rozpatrzymy prostą aplikację, realizującą funkcje sklepu internetowego i przyjrzymy się problemowi zabezpieczania przetwarzanych przez nią danych. By uczynić zadanie bardziej interesującym, założymy nieograniczony dostęp do bazy danych aplikacji, jej kodu źródłowego i haseł. Mimo wielu poruszanych tu zagadnień przykładowi temu daleko jest jeszcze do kompletności pod względem repertuaru faktycznie stosowanych w praktyce zabezpieczeń.

Narzędzia

Wykorzystamy hybrydową strategię ochrony, łączącą w sobie zalety algorytmów symetrycznych (szybkie szyfrowanie i deszyfracja) i asymetrycznych (większa trudność w złamaniu) - użyjemy mianowicie algorytmów Blowfish, RSA i dodatkowo SHA opartego o jednokierunkowe funkcje mieszające.

Niektóre z algorytmów szyfrujących zostały opatentowane i ich legalne wykorzystywanie uwarunkowane jest otrzymaniem licencji. Niektóre algorytmy mogą też być objęte embargiem eksportowym (importowym).

Krok 1. Generowanie klucza prywatnego i publicznego

Algorytmy asymetryczne szyfrują dane przy użyciu klucza publicznego, zaś do ich rozszyfrowania używają klucza prywatnego. Para powiązanych kluczy „publiczny-prywatny” generowana jest z użyciem liczb pseudolosowych, przy czym parametr startowy tej generacji („ziarno” - ang. randseed) określany jest przez użytkownika (jako liczba całkowita z zadanego przedziału). Wygenerowany klucz publiczny może być przechowywany gdziekolwiek, natomiast klucz prywatny, jako stanowiący ścisłą tajemnicę serwera, powinien być przechowywany w jemu tylko znanym sekretnym miejscu. Dla zwiększenia bezpieczeństwa zestaw używanych kluczy powinien być zmieniany co pewien czas.

Krok 2. Składanie zamówienia

Dla sprostania wymogom szybkości przy transakcji składania zamówienia wykorzystamy symetryczny algorytm Blowfish do zaszyfrowania danych użytkownika zawartych w treści zamówienia. W celu uzyskania klucza szyfrująco-deszyfrującego posłużymy się funkcjami mieszającymi wykorzystywanymi przez algorytm SHA. Bezpieczeństwo „mieszania” uwarunkowane jest w dużej mierze stopniem losowości szyfrowanych danych - jeżeli przykładowo czynnikiem losowym będzie jedynie czas dnia, haker może przeprowadzić analizę danych z poprzednich dni czy miesięcy; jeżeli losowość ta zostanie wzbogacona np. o aktualną datę, zawartość zegara procesora, aktualny stan zajętości zasobów systemu itp., zyska ona charakter wręcz niepowtarzalny i jako taka stanie się nieprzydatna dla wszelkich analiz porównawczych.

Krok 3. Szyfrowanie danych zamówienia

Po zaszyfrowaniu danych zamówienia należy zastanowić się nad sposobem ich przechowywania. Niektóre aplikacje rozdzielają poszczególne kategorie informacji, przechowując w oddzielnych polach zaszyfrowaną nazwę użytkownika, zaszyfrowany numer karty kredytowej, zaszyfrowany czas transakcji itp. Takie systemy działają na hakerów jak przysłowiowa płachta na byka - o ile bowiem nazwiska czy adresy cechują się raczej dużym stopniem nieregularności, to nie da się tego powiedzieć o numerach kart kredytowych (często rozpoczynających się od znanych hakerom sekwencji cyfr) i o terminach upływu ich ważności. Znacznie bezpieczniejsze jest łączenie poszczególnych pól w jeden blok i przechowywanie go w całości w postaci zaszyfrowanej (ang. CBC - Cipher Block Chaining); wydzielenie poszczególnych pól jest wówczas prawie niemożliwe.

Jeżeli separatorami poszczególnych pól przy łączeniu ich w pojedynczy blok były przecinki, rozbiór odszyfrowanego bloku na poszczególne pola łatwo można przeprowadzić, posługując się właściwością CommaText klasy TStringList.

Krok 4. Szyfrowanie użytego klucza

Unikatowy klucz sesji niezbędny jest do odszyfrowania danych zapisanych w serwerze, nie może on być jednak przechowywany w postaci jawnej. Do jego zaszyfrowania użyjemy asymetrycznego algorytmu RSA. W sposób opisany w kroku 1. uzyskujemy klucz publiczny (którym szyfrujemy unikatowy klucz sesji) i klucz prywatny (który posłuży później do odszyfrowania klucza sesji, a na razie powinien być przechowywany w miejscu znanym tylko serwerowi, na innym komputerze).

Krok 5. Odczytywanie danych zamówienia

Aby odszyfrować dane związane z zamówieniem, aplikacja WWW odczytuje (z sobie tylko znanej lokalizacji) klucz prywatny i za jego pomocą odszyfrowuje zaszyfrowany unikatowy klucz sesji. Ten ostatni służy następnie do odszyfrowania przedmiotowych danych.

Krok 6. Atak hakera

Jak już pisaliśmy wcześniej, nie ma zabezpieczeń absolutnie bezpiecznych, tak więc w przypadku konkretnej metody zabezpieczenia podstawowym pytaniem jest nie to, czy może być ona złamana, lecz to, jak wiele czasu i zasobów wymagałaby uwieńczona sukcesem próba jej złamania. W przypadku dobrego algorytmu szyfrującego próby rozszyfrowania zamówienia mogą trwać tak długo, aż dane zawarte w tym zamówieniu stracą jakąkolwiek użyteczność.

Powracając do analogii z najsłabszym ogniwem łańcucha, przeanalizujmy pod tym kątem przedstawiony schemat zabezpieczeń. Pierwszym z „ogniw” są klucze używane przez algorytm RSA - sam algorytm należy do bardzo bezpiecznych, natomiast bezpieczeństwo to może zostać zniweczone przez… nierozważny wybór miejsca, w którym przechowywany będzie klucz prywatny. Drugim ogniwem jest generowanie unikatowego klucza sesji - przy dostatecznym stopniu losowości algorytmu SHA trudno będzie hakerowi odgadnąć postać tego klucza.

Algorytm Blowfish wykorzystywany do szyfrowania danych zamówienia jest trzecim ogniwem omawianego łańcucha. Algorytm ten jest obecnie postrzegany jako jeden z najlepszych i bardzo trudny - o ile nie wręcz niemożliwy - do złamania.

Czwartym i ostatnim ogniwem jest monolityczne (CBC) szyfrowanie bloku stanowiącego konkatenację (lub inny sposób kombinacji) poszczególnych pól - przechowywanie informacji w postaci oddzielnych pól czyniłoby niniejsze ogniwo niezmiernie słabym.

HTML i XML

Jedną z najczęściej wymienianych nowości C++Buildera 5 jest obsługa języka XML (ang. Extensible Markup Language) skwapliwie wykorzystywanego przez komponenty grupy InternetExpress. W podrozdziale tym przedstawimy przykład wykorzystania komponentów tej grupy i wyjaśnimy, dlaczego XML jest dla nich idealnym językiem komunikacji.

XML

XML jest podzbiorem języka SGML (ang. Standard Generalized Markup Language), zaś HTML jest aplikacją (zastosowaniem) SGML. Tym, co odróżnia XML od HTML, jest zestaw reguł syntaktycznych tego ostatniego. Dokument XML również posługuje się sformalizowanym zbiorem reguł, lecz reguły te ustalane są przez autora dokumentu i specyfikowane w tzw. definicji typu dokumentu (ang. DTD - Document Type Definition); teoretycznie więc możliwe jest stworzenie DTD, opisującego reguły syntaktyczne HTML. Tak więc dokument XML w połączeniu ze swą specyfikacją DTD stanowi samoopisującą całość, co jest niewątpliwie bardzo użyteczne; sam dokument XML bez towarzyszącej mu specyfikacji DTD jest na ogół niezrozumiały.

Tym, co stanowi główną zaletę XML, jest jego prosty, czytelny format. W połączeniu z możliwością arbitralnego ustalania reguł syntaktycznych w postaci DTD możliwe jest więc opisanie w postaci XML niemal wszystkiego - od strony WWW i książki, poprzez bazy danych, aż po kompletne systemy operacyjne i systemy plików. W szczególności więc język ten mógłby posłużyć także do zapisu informacji produkowanych przez oprogramowanie, jak również do komunikacji pomiędzy częściami (warstwami) aplikacji rozproszonej.

Na razie jednak język XML nie wydaje się być czynnikiem zdolnym przełamać komputerową wieżę Babel; rok, dwa lata temu wiele narzędzi i aplikacji zdawało się wykazywać tendencję do eksportowania i importowania danych w formacie XML, podobnie jak dzisiaj większość aplikacji zwraca się (jednak) ku językowi HTML. Nie oznacza to bynajmniej, iż HTML ma szansę stać się komputerowym esperanto - tak jak nie sprawdziły się przewidywania sprzed pięciu lat, iż Java zdolna jest wyprzeć inne języki programowania (a propos, czy przypominasz sobie pakiet biurowy Corel's Java?).

Elementy obsługi XML spotykamy już w technologii MIDAS (Multitier Distributed Application Services), gdzie serwer aplikacji (DataSetProvider) zdolny jest komunikować się z klientem, będącym serwerem WWW (XMLProvider), który z kolei współpracuje z komponentem-producentem MidasPageProducer. W poprzednich wersjach MIDAS-a komponenty te wymieniały pomiędzy sobą informacje w specyficznym dla siebie formacie, w obecnej wersji jest to format XML, pozbawiony jednak specyfikacji DTD, w praktyce więc zrozumiały jedynie dla aplikacji stworzonych za pomocą Delphi 5 lub C++Buildera 5.

Innymi słowy, mimo iż serwery i klienci MIDAS-a stworzone za pomocą C++Buildera 5 mogą wymieniać ze sobą informację w formacie XML, to jednak w dalszym ciągu nie jest możliwe przyłączenie do DataSetProvidera klienta stworzonego w PowerBuilderze, ani też zmuszenie serwera Oracle8i do produkowania dokumentów XML zrozumiałych przez XMLBroker. Nawet w przypadku Delphi 5 dokument XML produkowany przez DataSetProvider wydaje się być użyteczny jedynie dla komponentu XMLBroker, który współpracuje jedynie z producentem MidasPageProducer. Dane pomiędzy DataSetProviderem a „regularnym” klientem (ClientDataSet) przesyłane są w dalszym ciągu w specyficznym, nieudokumentowanym formacie.

W tym kontekście komponenty grupy InternetExpress wydają się być przełomem, bowiem komunikacja pomiędzy warstwami aplikacji odbywa się w formacie XML, jakkolwiek wymieniane dokumenty zrozumiałe są jedynie w ramach technologii MIDAS. Cóż, to dopiero początek…

InternetExpress

Komponenty InternetExpress łączą w sobie elementy technologii MIDAS i WebBroker, stanowią więc udany mariaż technologii aplikacji wielowarstwowych i mechanizmów internetowych. Stworzone z ich udziałem aplikacje (w środowisku Delphi, C++Buildera i JBuildera) pełnią rolę klientów współpracujących z serwerami MIDAS-a.

Przykład - kartoteka zamówień

W naszym przykładowym projekcie aplikacje klienta stworzone za pomocą komponentów InternetExpress pobierają dane z serwera MIDAS-a. W niniejszym rozdziale nie będziemy zajmować się funkcjonowaniem serwerów tego typu, ograniczając się jedynie do aplikacji klienta.

TMidasPageProducer

Powróćmy do naszej aplikacji ISAPI Webshow.dll; zawiera ona jeszcze dwa komponenty akcji, których dotąd nie wykorzystywaliśmy: /browse i /final. Poświęcimy im pozostałą część rozdziału.

Niewątpliwie musimy rozpocząć od połączenia z serwerem MIDAS-a. Jako że jest to połączenie ze zdalnym modułem danych (po stronie serwera), użyjemy komponentu TDCOMConnection ze strony MIDAS palety komponentów. Komponent ten należy umieścić w module WWW naszej aplikacji i za pośrednictwem jego właściwości ServerName należy wybrać pozycję „MidasServer.CustomerOrders” - pierwszy człon nazwy jest nazwą serwera, drugi natomiast nazwą ko-klasy zdalnego modułu danych na tymże serwerze (stąd prosty wniosek, iż pojedynczy serwer MIDAS-a może posiadać kilka modułów danych). Aby ostatecznie nawiązać połączenie z serwerem, należy ustawić na true właściwość Connected komponentu TDCOMConnection - zarejestrowany serwer MIDAS-a zostanie uruchomiony, manifestując ten fakt stosowną ikoną na pulpicie. Możemy więc już pobierać dane z jego modułu danych - potrzebne nam będą do tego dwa komponenty ze strony InternetExpress palety komponentów: TXMLBroker i TMidasPageProducer, umieśćmy więc po jednym egzemplarzu każdego z nich w naszym module WWW i przypiszmy komponent TDCOMConnection do właściwości RemoteServer komponentu TXMLBroker. Musimy jeszcze przypisać do właściwości ProviderName odpowiedni komponent TDataSetProvider zlokalizowany na zdalnym serwerze - w naszym przypadku rozwijalna lista zawiera tylko jedną pozycję (DataSetProvider1), choć sam fakt wyboru świadczy o tym, iż serwer MIDAS-a może posiadać kilka komponentów tego typu.

Połączenie z serwerem zostało już w pełni nawiązane, przystąpmy więc do projektowania wynikowej strony WWW produkowanej przez TMidasPageProducer.

Web Page Editor

Na pierwszej pozycji menu kontekstowego komponentu TMidasPageProducer znajduje się opcja Web Page Editor - jej wybranie spowoduje uruchomienie edytora o takiej właśnie nazwie; za jego pomocą możemy zaprojektować wygląd wynikowej strony WWW. Edytor umożliwia podgląd zawartości strony zarówno w formacie źródłowym HTML, jak i w postaci typowej dla przeglądarki WWW (patrz rys. 10.19). W górnej części okna edytora znajdują się dwa panele: lewy ukazuje komponenty użyte na potrzeby TMidasPageProducera, zaś prawy - komponenty potomne (pierwszego poziomu) komponentu podświetlonego aktualnie w lewym panelu.

Mimo iż Web Page Editor umożliwia tworzenie komponentów, nie posiada on niczego na kształt palety komponentów. Tworzenie nowych komponentów odbywa się tu bowiem za pomocą dialogu New Component uruchamianego z menu kontekstowego górnych paneli. Dialog ten oferuje do wyboru komponenty, które mogą stać się komponentami potomnymi w stosunku do pozycji podświetlonej aktualnie w lewym panelu. Początkowo panel ten zawiera jedyną pozycję reprezentującą TMidasPageProducer, możemy wówczas utworzyć jeden z trzech komponentów potomnych: DataForm, QueryForm lub LayoutGroup. Gdy wybierzemy DataForm i uruchomimy dialog ponownie, zaoferuje on nam do wyboru komponenty: DataGrid, DataNavigator, FieldGroup i LayoutGroup. Wybierzmy (trzymając klawisz Ctrl) jednocześnie dwa: DataNavigator i FieldGroup.

Edytor ostrzeże nas w tym momencie (w oknie podglądu), iż wskaźniki DataNavigator1.XMLComponent i FieldGroup2.XMLBroker są wskaźnikami pustymi (nil). Ustawmy więc pierwszy z wymienionych wskaźników na komponent FieldGroup, w wyniku czego zniknie pierwsze z ostrzeżeń. Drugiemu wskaźnikowi przypiszmy komponent XMLBroker1 (z modułu WWW) w wyniku czego pozbędziemy się również drugiego ostrzeżenia. Jednocześnie w oknie podglądu ukaże się lista wszystkich pól kartoteki kontrahentów, wraz ze specjalnym polem statusu (jako ostatnim). Pole to zawiera jednoznakowy znacznik, informujący, czy dany rekord został wstawiony, zmodyfikowany albo usunięty, i jako takie jest może interesujące dla projektanta, lecz jego użyteczność dla użytkownika końcowego jest raczej wątpliwa. Domyślnie widok zawiera wszystkie pola kartoteki, możemy jednak łatwo usunąć niepotrzebne z nich i zmienić kolejność pozostałych, wybierając w lewym panelu komponent FieldGroup i następnie kolejno uruchamiając menu kontekstowe poszczególnych pozycji na prawym panelu, co pokazuje rysunek 10.19.

Rysunek 10.19. Web Page Editor na etapie projektowania

Uruchomienie aplikacji

Przed uruchomieniem aplikacji stworzonej w technologii InternetExpress musimy najpierw upewnić się, czy biblioteka ISAPI (WebShow.dll) znajduje się w katalogu skryptów serwera WWW pracującego w lokalnym komputerze. Aby zapewnić, iż zawsze będziemy mieli do czynienia z najnowszą wersją biblioteki, najlepiej ustawić ten katalog jako wynikowy dla produkowanych przez projekt binariów. Nie uda się to jednak w przypadku serwera IIS, który nie pozwoli na nadpisanie używanej biblioteki ISAPI.

Następnie musimy przypisać komponent TMidasPageProducer do właściwości Producer tego komponentu akcji, który odpowiada żądaniu /browse.

W końcu musimy zapewnić aplikacji dostęp do specjalnych plików JavaScriptu, niezbędnych do analizy generowanych pakietów XML. Pliki te znajdują się na dysku dystrybucyjnym C++Buildera 5 w katalogu CBuilder5\Source\WebMidas, zajmując łącznie około 60 KB, najlepiej więc przekopiować je do katalogu skryptów serwera WWW, jednocześnie przypisując ten katalog właściwości IncludePathURL komponentu TMidasPageProducer (w przypadku posługiwania się programem IntraBob należy pliki te przekopiować do katalogu, w którym program rezyduje wraz z biblioteką ISAPI i ustawić wspomnianą właściwość na „./”).

Po wykonaniu tych czynności należy ponownie skompilować projekt WebShow w trybie Build i uruchomić bibliotekę WebShow.dll, używając Internet Explorera w wersji 4. lub wyższej, Netscape Communicatora w wersji 4. lub wyższej albo programu IntraBob w wersji 5. - uzyskany widok przedstawia rysunek 10.20.

Rysunek 10.20. Strona WWW wyprodukowana przez InternetExpress w odpowiedzi na żądanie /browse

Zagnieżdżone tabele - układ „master-details”

W ostatnim przykładzie rozdziału zaprezentujemy wyświetlenie zamówień przyporządkowanych poszczególnym klientom w relacji „master-details”, podobnie jak na rysunku 10.16 - z tą jednak różnicą, iż wyświetlenie to odbędzie się w całości w pojedynczym oknie, z możliwością pełnego nawigowania. W tym celu uruchom ponownie Web Page Editor (za pomocą menu kontekstowego komponentu TMidasPageProducer) i dodaj do formularza DataForm komponent DataGrid i kolejny komponent DataNavigator. Następnie przypisz do właściwości XMLBroker komponentu DataGrid (jedyny) komponent TXMLBroker. Spowoduje to zniknięcie ostrzeżenia, lecz wynik będzie inny od oczekiwanego: zamiast pól tabeli zamówień (Orders) ukażą się pola tabeli kontrahentów (Customers). Musisz więc przejść do komponentu FieldGroup1 (w lewym panelu) i przypisać do jego właściwości XMLDataSetField tabelę TableOrders jako tabelę zagnieżdżoną. Ponieważ standardowo ukażą się wszystkie pola zamówień, pozostaw tylko te najważniejsze (usuwając pozostałe w sposób wcześniej opisany), w tym również pole StatusColumn1, które tym razem zawierać będzie użyteczną dla nas informację.

Szerokość produkowanego widoku zależna jest oczywiście od szerokości wyświetlanych pól, co widać na rysunku 10.12; niektóre z nich przydałoby się nieco zwęzić - na przykład szerokość pól zawierających datę można by zmniejszyć do 12 (modyfikując właściwość DisplayWidth), można by też skrócić tytuły niektórych pól, zmieniając np. „PaynmentMethod” na „Paynment”.

Rysunek 10.21. Web Page Editor w czasie projektowania widoku „master-details”

Mimo iż nasz widok ma na razie wygląd nieco surowy - bez szczególnej kolorystyki, czcionek, grafiki itp. - rozpoczniemy testowanie aplikacji. Jej atrakcyjna szata graficzna jest co prawda dość istotna, jednak najważniejsze jest oczywiście bezbłędne działanie.

Widok wyświetlony w odpowiedzi na żądanie /final przedstawiony jest na rysunku 10.22.

Rysunek 10.22. Ostateczny widok w układzie „master-details”

Wyświetlana strona zawiera wszystkie żądane elementy: HTML z tabelą i definicjami wprowadzanych danych odpowiada za wygląd, XML zawiera tabelę i definicje pól zgodnie z aktualnymi danymi, wreszcie JavaScript dokonuje analizy danych XML i wbudowuje je w informację wejściową dla strony WWW. To wszystko!

Zauważyłeś zapewne, iż podczas przeglądania danych wyświetlanie strony WWW wolne jest od migotania - „stała” część strony pozostaje bowiem niezmienna, zmieniają się tylko zawartości kontrolek edycyjnych. To efekt współpracy XML i JavaScriptu.

Nasz projekt ma niestety jedną słabą stronę - dla danego kontrahenta ściągany jest kompletny zbiór jego zamówień, co w przypadku dużych baz może niesamowicie spowolnić wyświetlanie. Lekarstwem na tę przypadłość jest ustawienie właściwości MaxRecords komponentu TXMLBroker na taką liczbę rekordów, jaką chcemy faktycznie zobaczyć.

Spoglądając na rysunek 10.12, zauważymy, iż pole „CustNo” ma wygląd nieco inny niż pozostałe; otóż pole to, jako sprzęgające tabele „master” i „details”, jest polem tylko do odczytu. Pozostałe pola można swobodnie modyfikować, co dla każdego zmodyfikowanego rekordu spowoduje ukazanie się literki M w polu statusu.

Należy jeszcze zwrócić uwagę na ważny fakt, iż wszelkie zmiany dokonywane na wyświetlanym widoku mają na razie charakter prowizoryczny - baza na zdalnym serwerze pozostaje niezmieniona. Aby utrwalić zmiany, należy kliknąć przycisk „Apply Updates”; spowoduje to wysłanie do serwera pakietu XML z żądaniem aktualizacji, w wyniku czego ze strony serwera nadejść mogą komunikaty o błędach wymagających rozwiązania (ang. reconcilation).

Tak mi szaro…

Mimo poprawnego działania nasza aplikacja prezentuje się nieco ponuro, niczym (nie przymierzając) podręczne narzędzie programistyczne, nie zaś produkt mający dawać zadowolenie użytkownikowi. Jednym ze sposobów nadania jej bardziej atrakcyjnego wyglądu jest edycja właściwości HTMLDoc komponentu TMidasPageProducer; rozwiązaniem bardziej elastycznym jest zapisanie szablonu strony w zewnętrznym pliku o ustalonej nazwie, przypisanej właściwości HTMLFile tegoż komponentu, co umożliwi użytkownikowi modyfikowanie wyglądu strony bez konieczności sięgania do kodu źródłowego projektu.

Można także manipulować właściwościami poszczególnych kolumn w ramach komponentu FieldGroup lub właściwościami poszczególnych komórek w ramach komponentu DataGrid (w edytorze Web Page Editor).

Przykładowy wygląd uatrakcyjnionego widoku przedstawia rysunek 10.23.

Rysunek 10.23. Widok „master-details” w nowej szacie graficznej

Podsumowanie

Zakończony właśnie rozdział poświęcony został zagadnieniu programowania serwerów WWW w oparciu o technologię WebBroker i związane z nią komponenty C++Buildera. Stworzyliśmy prosty, lecz w pełni funkcjonalny projekt oparty na bibliotece typu ISAPI, ilustrujący podstawowe zasady budowania aplikacji-rozszerzeń serwera i prezentujący osiągane dzięki temu rezultaty. Poruszyliśmy także elementarne zagadnienia związane z bezpieczeństwem aplikacji WWW.

Na zakończenie zajęliśmy się komponentami z grupy InternetExpress, ilustrując prostymi przykładami ich współpracę z serwerem MIDAS-a.

Algorytm RSA opiera swe bezpieczeństwo na różnicy pomiędzy trudnością stwierdzenia, czy duża (powiedzmy 200-cyfrowa) liczba naturalna jest liczbą pierwszą (co jest potrzebne do wygenerowania kluczy) a trudnością rozkładu iloczynu dwóch liczb pierwszych na czynniki (umożliwiałoby to obliczenie klucza prywatnego na podstawie klucza publicznego). Przy obecnym stanie wiedzy algorytmicznej rozłożenie na czynniki 400-cyfrowej liczby, będącej iloczynem dwóch liczb pierwszych, zajęłoby najszybszym nawet (obecnym) komputerom średnio kilka milionów lat - przyp. tłum.

Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

1

2 D:\helion\C++Builder 5\R10-03.DOC



Wyszukiwarka

Podobne podstrony:
ocena aktualnego 739 ocena 1 id Nieznany
(00915) Crow Creek Bridgeid 739
ocena aktualnego 739 ocena 2 id Nieznany
738 739
sciaga 739
ocena aktualnego 739 opinia AWF Nieznany
739
739
739
739
739, Cele i funkcje wychowania muzycznego
739
739
ocena aktualnego 739 ocena 1 id Nieznany
Nuestro Circulo 739 CAMKPEONATO ARGENTIN0 2016 MASCULINO 15 de octubre de 2016
000 739
738 739

więcej podobnych podstron