r 13 00 SEU323D75PN65G4OWK5NMMW5VVYAD35OC43B2CI


Programowanie serwerów
WWW
Niniejszy rozdział 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
niniejszym 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łączonym CD ROMie 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 klienckie. 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. 13.1):
Tu proszę wkleić rysunek z pliku ORIG-13-1.BMP
Rysunek 13.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 odpowiedz 
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 dalszym ciągu
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 13.1 nastąpi utworzenie
pustego modułu danych i związanego z nim modułu zró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 13.2) oraz na stronie
InternetExpress, którą zajmiemy się nieco pózniej.
Tu proszę wkleić rysunek z pliku ORIG-13-2.BMP
Rysunek 13.2 Strona Internet palety Komponentów
Spoglądając na rysunek 13.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ąc 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 w wielokropek w linii 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 13.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.
Tu proszę wkleić rysunek z pliku ORIG-13-3.BMP
Rysunek 13.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
Odpowiedz 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 = "

Hello, world!

"
}
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 = "

Hello, world!

";
if (Request->Method == "GET")
Response->Content = Response->Content + "GET" +
"
Query: " + Request->Query;
else
if (Request->Method == "POST")
Response->Content = Response->Content + "POST" +
"
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 swego 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łączonym CD ROMie 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 13.4:
Tu proszę wkleić rysunek z pliku ORIG-13-4.BMP.
Rysunek 13.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 zródłowym
ustanowiony zostanie punkt przerwania (breakpoint). Ustawmy go więc w jednej z początkowych
linii funkcji zdarzeniowej komponentu WebActionItem1 (rys. 13.5):
Tu proszę wkleić rysunek z pliku AG-13-A.BMP
Rysunek 13.5 Ustawienie punktu przerwania w kodzie zró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:


WebBroker HTML Form





Name:






Po wyświetleniu tej strony w przeglądarce WWW, wpisaniu czegokolwiek w pole nazwy i
kliknięciu w przycisk 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 13.6.
SCAN
Tu proszę zeskanować rysunek 13.5 ze strony 576 oryginału
Rysunek 13.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. 13.7):
SCAN
Tu proszę zeskanować rysunek 13.6 ze strony 577 oryginału
Rysunek 13.7 Zatrzymanie wykonywania biblioteki DLL na punkcie przerwania
Początek Ostrzeżenia
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->CreateForm(__classid(TWebModule1), &WebModule1);
Application->Run();
}
catch (Exception &exception)
{
}
return 1;
}
Koniec ostrzeżenia
Po ponownym uruchomieniu (F9) zatrzymanej w punkcie przerwania biblioteki otrzymamy stronę
stanowiącą odpowiedz na wysłane żądanie (rys. 13.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  % .
SCAN
Tu proszę zeskanować rysunek 13.7 ze strony 578 oryginału
Rysunek 13.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 sekcjach zaprezentujemy teraz 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ż strona stanowiąca rezultat odpowiedzi powinna być
oparta na predefiniowanym szablonie, 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:

TPageProducer




<#Greeting> <#Name>


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 13.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 zdarzenie OnHTMLTag:
Wydruk 13.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";
}
Skojarzenie 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 13.9; należy przy tym pamiętać o właściwym
ustawieniu wartości PathInfo (na /hello) bądz to w oknie programu IntraBob, bądz
wprost w treści formularza:


SCAN
Tu proszę zeskanować rysunek 13.8 ze strony 581 oryginału
Rysunek 13.9 Strona HTML wyprodukowana przez TPageProducer
Niektóre edytory stron HTML (szczególnie FrontPage) umożliwiają ujmowanie parametrów
znaczników w podwójne 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:

BIOLIFE Info





Category: <#Category>

Common_Name: <#Common_Name>

Species Name: <#Species Name>

Notes: <#Notes>
Przejdz 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:

Wynikową stronę HTML przedstawia rysunek 13.10.
SCAN
Tu proszę zeskanować rysunek 13.9 ze strony 583 oryginału
Rysunek 13.10 Strona HTML wyprodukowana przez TDataSetPageProducer
Uważny Czytelnik spostrzeże natychmiast, iż na rysunku 13.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 13.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 wyrazny 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 13.11.
SCAN
Tu proszę zeskanować rysunek 13.10 ze strony 584 oryginału
Rysunek 13.11 Poprawiona zawartość strony z rysunku 13.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
13.12.
SCAN
Tu proszę zeskanować rysunek 13.11 ze strony 586 oryginału
Rysunek 13.12 Edytor kolumn komponentu TDataSetTableProducer
Przy pierwszym uruchomieniu edytora ujrzymy wszystkie pola tabeli customer.db. To
standardowe zachowanie wszystkim komponentów pozostający 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 w 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 13.13
SCAN
Tu proszę zeskanować rysunek 13.12 ze strony 587 oryginału
Rysunek 13.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:


WebBroker HTML Form





CustNo:






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 . Uwaga ta odnosi
się także do komponentu TDataSetTableProducer.
Rysunek 13.14 przedstawia wyświetlenie wyników zapytania w sytuacji, gdy w pole Custno na
formularzu wpisano 1221.
SCAN
Tu proszę zeskanować rysunek 13.13 ze strony 589 oryginału
Rysunek 13.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 13.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
" CellData + "\">" + CellData + "";
#else
// formularz
(AnsiString)"
" +
"" +
"" +
"
";
#endif
else
if (CellData == "") BgColor = "Silver";
else
if ((CellColumn == 6) && (CellData.Pos("US") > 0))
BgColor = "Red";
}
Na rysunku 13.15 widzimy znajomą kartotekę kontrahentów ze zmodyfikowaną kolumną
CustNo, rysunek 13.16 przedstawia natomiast listę zamówień wybranego kontrahenta.
SCAN
Tu proszę zeskanować rysunek 13.14 ze strony 591 oryginału
Rysunek 13.15 Wyświetlenie kartoteki kontrahentów za pomocą komponentu
TDataSetTableProducer, z hiperłączami w pierwszej kolumnie
SCAN
Tu proszę zeskanować rysunek 13.15 ze strony 591 oryginału
Rysunek 13.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ęć w 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 
uczyniliśmy to już w przypadku hiperłączy w wyświetlanej kartotece kontrahentów
" CellData + "\">" + CellData + "";
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
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śla 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 cookies ma się odbywać
z użyciem bezpiecznego połączenia.
Zapisanie cookie w komputerze użytkownika to jednak dopiero połowa roboty; 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>:

BIOLIFE Info







<#RecNo>

Category: <#Category>

Common_Name: <#Common_Name>

Species Name: <#Species Name>

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

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 13.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 =
" " + IntToStr(TableBiolife->RecNo) +"/"+
IntToStr(TableBiolife->RecordCount) + "

";
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 13.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 13.17  różni się on od tego z
rysunku 13.11 zestawem przycisków i informacją o liczbie rekordów oraz numerze rekordu
bieżącego.
SCAN
Tu proszę zeskanować rysunek 13.16 ze strony 596 oryginału
Rysunek 13.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ą unikalnych 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ślniejszymi atakami hakerskimi 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
zabezpieczenia 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 zró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 prze aplikację CGI lub bibliotekę ISAPI rozpoczyna się
nagłówkiem HTTP, po którym następuje pusta linia 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 pierwszą linię  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 13.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 pierwszej linii, zaś kolejne informacje na temat statusu strony są po
prostu ignorowane. Aby zmusić serwer WWW do rezygnacji z dołączania tej linii, 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 13.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
Aatwość 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 Perl a.
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 13.18 stanowi jeden z dowodów
na to, jak złudne jest poczucie bezpieczeństwa oparte o hasła przechowywane 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óżnej maści dekompilatorów i disasemblerów.
SCAN
Tu proszę zeskanować rysunek 13.17 ze strony 601 oryginału
Rysunek 13.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ózniej 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 unikalnych 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
zró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
Unikalny 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
unikalny klucz sesji) i klucz prywatny (który posłuży pózniej 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 unikalny 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łamania1. 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 unikalnego 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 niniejszej sekcji 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
1
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.)
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 MIDASa komponenty te
wymieniały pomiędzy sobą informację 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 klienty MIDASa 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 DataSetProvider a 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
DataSetProvider em 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 MIDASa.
Przykład  kartoteka zamówień
W naszym przykładowym projekcie aplikacje klienckie stworzone za pomocą komponentów
InternetExpress pobierają dane z serwera MIDASa. Serwerowi temu poświecony jest
rozdział 19, tam też znajduje się kompletny projekt, którego celem jest zarejestrowanie i
uruchomienie tego serwera na lokalnym komputerze. W niniejszym rozdziale ograniczymy się
tylko do  klienckiej strony aplikacji.
TMidasPageProducer
Zakładając, iż projekt serwera z rozdziału 19 został zarejestrowany, 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ęść niniejszego rozdziału.
Niewątpliwie musimy rozpocząć od połączenia z serwerem MIDASa. 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 MIDASa 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 MIDASa 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 MIDASa 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 zródłowym HTML, jak i w postaci typowej dla
przeglądarki WWW (patrz rys. 13.19). W górnej części okna edytora znajdują się dwa panele:
lewy ukazuje komponenty użyte na potrzeby TMidasPageProducer a, 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ż wskazniki
DataNavigator1.XMLComponent i FieldGroup2.XMLBroker są wskaznikami pustymi
(nil). Ustawmy więc pierwszy z wymienionych wskazników na komponent FieldGroup,
wyniku czego zniknie pierwsze z ostrzeżeń. Drugiemu wskaznikowi 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 jedna ł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 13.19.
SCAN
Tu proszę zeskanować rysunek 13.18 ze strony 609 oryginału
Rysunek 13.19 Web Page Editor na etapie projektowania
Uruchomienie aplikacji
Przed uruchomieniem aplikacji stworzonej w technologii InternetExpress musimy wpierw
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 60KB,
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 powyższych 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 13.20.
SCAN
Tu proszę zeskanować rysunek 13.19 ze strony 610 oryginału
Rysunek 13.20 Strona WWW wyprodukowana przez InternetExpress w odpowiedzi na
żądanie /browse
Zagnieżdżone tabele  układ  master details
W ostatnim przykładzie niniejszego rozdziału zaprezentujemy wyświetlenie zamówień
przyporządkowanych poszczególnym klientom w relacji  master details , podobnie jak na
rysunku 13.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 13.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 .
SCAN
Tu proszę zeskanować rysunek 13.20 ze strony 611 oryginału
Rysunek 13.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 naszej 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 13.22.
SCAN
Tu proszę zeskanować rysunek 13.21 ze strony 612 oryginału
Rysunek 13.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 13.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 spowodować utrwalenie poczynionych zmian, należy kliknąć w 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 zró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 13.23.
SCAN
Tu proszę zeskanować rysunek 13.22 ze strony 613 oryginału
Rysunek 13.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 MIDASa.


Wyszukiwarka


Podobne podstrony:
13 00 Roboty specjalistyczne
13 00 23 a3jjrjqbta5bc52vgazto6k3o2p5nm3odmyvc6i
006 00 (13)
TI 00 09 13 T pl(2)
TI 00 10 13 T pl(2)
WSM 00 13 pl
egzamin 00 06 13
UAS 13 zao
er4p2 5 13

więcej podobnych podstron