Delphi i PHP Komunikacja Pomiędzy Delphi i PHP można stworzyć własne aplikacje do komunikacji pomiędzy tymi językami. Służy do tego metoda POST. Dowiesz się... W jaki sposób odebrać dane ze skryptu. W jaki sposób wysłać dane do skryptu. Poziom trudności Sposób komunikacji, opiera się na metodzie POST, która umożliwia wysyłanie danych przy pomocy nagłówków HTTP. Dane przesyłane tą metodą mają postać: pole1=wartość&pole2=wartość2. Tak więc: nazwa pola jest oddzielona od jego wartości znakiem =, natomiast poszczególne porcje danych oddziela znak &. Przykładowy nagłówek z powyższymi informacjami wyglądałby więc tak, jak na Listingu 1. Powinieneś wiedzieć... Powinieneś znać podstawy języka i środowiska Delphi. Powinieneś znać podstawy języka PHP i HTML. Powinieneś znać podstawowe pojęcia związane z protokołem http. Z Delphi do PHP Zacznijmy projektować naszą aplikację, która prześle dane do skryptu metodą POST. Uruchamiamy Delphi i z menu File wybieramy New Application. Po instalacji pakietu INDY w Delphi, przybędzie nam kilka nowych zakładek na palecie komponentów. Odszukajmy zakładkę Indy Clients i wybierzmy z niej komponent IdHTTP. To on umożliwi nam wysyłanie danych. Umieścimy jeszcze następujące komponenty: 3x Edit, 4x Label, 1x Button, oraz 1x Memo (wszystkie one znajdują się w zakładce Standard). Ich przykładowe rozmieszczenie, a także właściwości Caption jakie powinniśmy im nadać, zostały zaprezentowane na Rysunku 1. Po umieszczeniu wszystkich elementów, kliknijmy tylko raz na komponent IdHTTP i przejdźmy do Object Inspector a, a następnie odszukajmy listę Request. Po jej rozwinięciu ukaże się nam kilka atrybutów, interesować nas będzie ten o nazwie ContentType. W to pole musimy wpisać ciąg: application/x-www-form-urlencoded. Więcej o kodowaniu danych możemy przeczytać w ramce Kodowanie danych. Pisanie kodu Projektowanie aplikacji mamy już za sobą, czas więc na napisanie kilku linijek kodu. Nasz program będzie miał za zadanie: pobrać adres strony; pobrać dwie zmienne do wysłania; wysłać dane; odebrać kod wynikowy. Jego lista zadań jest krótka. Dzięki zastosowaniu komponentu IdHTTP, kod również będzie krótki. Kliknijmy dwa razy na Button ie i wpiszmy kod, który znajduje się na Listingu 2. Przyjrzyjmy się linijce odpowiadającej za utworzenie strumienia StreamIn, a szczególnie fragmentowi: (Format('text1=%s&text2=%s', [Edit2.Text,Ed it3.text])). Jak pamiętamy, dane przesyłane metodą POST mają postać: pole1=wartość&pole2=wartość2. Dlatego musimy je przesłać w taki sposób. Nazwy pól: text1 i text2 są nazwami przykładowymi, które następnie zastosujemy w skrypcie odbierającym dane. Należy pamiętać, że będą one różne dla każdej strony, która odbiera pakiety z danymi. Powyższa linijka tworzy ciąg powstały z połączenia tekstu w cudzysłowie, z tekstem wpisanym w Edit2 i Edit3. Owy ciąg jest następnie wysyłany do skryptu za pomocą funkcji POST komponentu IdHTTP. Ostatnim krokiem będzie zapisanie i skompilowanie naszego projektu. Gdy mamy już gotową aplikację, możemy stworzyć najprostszy plik PHP, który posłuży nam do jej testowania. Propozycja kodu znajduje się na Listingu 3. W skrypcie, dla ułatwienia, zostały zastosowane zmienne o takich samych nazwach, jak w naszym programie. Po zapisaniu pliku PHP i umieszczeniu go na serwerze, uruchamiamy naszą aplikację, podajemy odpowiednie parametry i naciskamy przycisk POST. Pole tekstowe Memo podpisane jako Kod wynikowy zostanie automatycznie uzupełnione danymi zwróconymi przez skrypt. Przedstawia to Rysunek 2. Listing 1. Przykładowy nagłówek z danymi wysłanymi metodą POST POST /index.php HTTP/1.1 Host: www.domena.com User-Agent: Mozilla/5.0 Content-Length: 28 Content-Type: application/x-www-formurlencoded pole1=wartość&pole2=wartość2 26 06/2007
Delphi i PHP Listing 2. Wysyłanie danych z aplikacji do skryptu metodą POST procedure TForm1.Button1Click(Sender: TObject); StreamIn,StreamOut: TStringStream; try {Tworzymy strumień StreamIn zawierający łańcuch danych, zostanie on potem wysłany do skryptu} StreamIn := TStringStream.Create(Format('text1=%s&text2=%s', [Edit2.Text,Edit3.Text] )); {Tworzymy pusty strumień StreamOut. Będzie on odpowiedzialny za odbiór kodu wynikowego skryptu} StreamOut := TStringStream.Create(''); {Procedura POST komponentu IdHTTP i jej kolejne parametry: Adres URL, strumień z danymi, strumień odbierający} IdHTTP1.Post(Edit1.Text,StreamIn,StreamOut); {Wyświetlamy kod wynikowy ze strumienia w Memo} Memo1.Lines.Text:=StreamOut.DataString; finally {Zwalniamy strumienie} StreamIn.Free; StreamOut.Free; Listing 3. Odbieranie danych z aplikacji wysłanych metodą POST <?php $text1=$_post['text1']; $text2=$_post['text2']; echo "Zmienna \"text1\" to: $text1\n"; //wyświetlamy zmienną text1 echo "Zmienna \"text2\" to: $text2\n"; //wyświetlamy zmienną text2?> Przykładowe zastosowanie Przykładowym zastosowaniem powyższej metody komunikacji może być program, który będzie uploadował wybrane obrazy z naszego dysku twardego na darmowy hosting zdjęć ImageShack (dostępny pod adresem www.imageshack.us). Zaczniemy od małego rekonesansu, tak więc otwieramy ową stronę. Aby uniknąć żmudnej analizy kodu w celu wydobycia szczegółów, skorzystajmy z wtyczki do Firefox a pod nazwą Web Developer (link znajduje się w ramce W Sieci ). Posiada ona funkcję, która modyfikuje wyświetlaną stronę pokazując, na przykład, szczegóły formularzy. Zostało to pokazane na Rysunku 3. Co na nim widzimy? Po pierwsze formularz odwołuje się do strony głównej ImageShack.us (fragment form action= ), po drugie- format kodowania danych to multipart/ form-data (fragment enctype= ), i wreszcie po trzecie- pole, z którego pobierana jest ścieżka do wysyłanego pliku (input name="fileupload ). Posiadając te informacje możemy rozpocząć projektowanie aplikacji. Przykładowa aplikacja Zacznijmy od stworzenia nowego projektu. Potrzebne nam będą komponenty: 1x Edit, 1x Memo, 1x button, a także komponent IdHTTP. Podobnie jak ostatnio kliknijmy raz na komponent IdHTTP i przejdźmy do Object Inspector a, a następnie odszukajmy listę Request. Po jej rozwinięciu ukaże nam się kilka atrybutów; interesować nas będzie ten o nazwie ContentType. W to pole musimy wpisać ciąg: multipart/form-data. Rysunek 3. Wtyczka Web Developer wyświetlająca szczegóły formularza na stronie ImageShack.us Teraz, kliknijmy dwa razy na komponent Button, i w edytorze kodu wpiszmy fragment z Listingu 4. Jak widać została tam zawarta zmienna MyData typu TIdMultiPartFormDataStream. Jest to specyficzny typ strumienia Rysunek 1. Przykładowe rozmieszczenie komponentów. Z lewej strony komponenty ze standardowymi właściwościami Caption, natomiast z prawej komponenty posiadające zmodyfikowaną tę właściwość Rysunek 2. Gotowa aplikacja wysyłająca dane do skryptu www.phpsolmag.org 27
z danymi, którego implementacja konieczna jest do wysłania danych, zakodowanych w formacie multipart/form-data. Aby z niego skorzystać, konieczne jest dodanie do sekcji Uses modułu IdMultipartFormData. Jest on dołączony do pakietu INDY. Po kompilacji projektu należy w pole Edit wpisać ścieżkę obrazu, który chcemy uploadować, a następnie nacisnąć przycisk Button. Zachęcam do przejrzenia kodów źródłowych, które zostały dołączone do płyty. Do aplikacji dodałem również parser, który pobiera z kodu wynikowego linki do naszego obrazu, ale to temat na inny artykuł. Przykład działania aplikacji pokazany jest na Rysunku 4. Z PHP do Delphi... Teraz nadszedł czas na napisanie programu, który będzie umożliwiał odbieranie zmiennych wysyłanych przez skrypt. Stworzymy nową aplikację i na formie umieścimy komponenty: 2x Label, 1x Edit Listing 4. Wysyłanie danych z aplikacji do strony ImageShack procedure TForm1.Button1Click(Sender: TObject); MyData: TIdMultiPartFormDataStream; {tworzymy specyficzny strumień z danymi} MyData:=TIdMultiPartFormDataStream.Create; try {kolejny krok to dodanie do strumienia, metodą AddFile, kolejno: nazwy pola (fileupload),ścieżki pliku, a także typu zawartości pakietu z danymi(file)} MyData.AddFile('fileupload',Edit1.Text,'file'); {kod wynikowy zostanie wyświetlony w Memo1} Memo1.Lines.Text:=IdHTTP1.Post('http://imageshack.us/',MyData); finally {Zwalniamy strumień} MyData.Free; Listing 5. Aktywacja i deaktywacja serwera IdHTTPServer1 {Procedura OnCreate} procedure TForm1.FormCreate(Sender: TObject); {Ustawiamy numer portu dla serwera} IdHTTPServer1.DefaultPort := 8008; {aktywujemy serwer} IdHTTPServer1.Active:=True; {- - -} {procedura OnClose} procedure TForm1.FormClose(Sender: TObject; Action: TCloseAction); {dezaktywujemy serwer} IdHTTPServer1.Active:=False; {- - -} oraz 2x Memo (wszystkie one znajdują się w zakładce Standard). Ostatnio użyliśmy komponentu, który był klientem protokołu HTTP. Pisząc tę aplikację, będziemy musieli stworzyć serwer HTTP. Odszukajmy więc zakładkę Indy Servers i wybierzmy z niej komponent IdHTTPServer. Ich przykładowe rozmieszczenie, a także właściwości Caption jakie powinniśmy im nadać, zostały zaprezentowane na Rysunku 5. Pisanie kodu Przyszedł czas na oprogramowanie naszej aplikacji. Zacznijmy od aktywacji serwera- klikamy na formie i odszukujemy właściwość OnCreate na zakładce Events w oknie Object Inspector i wpisujemy tam fragment kodu z Listingu 5. podpisanego jako: {Procedura OnCreate}. Deaktywacja serwera odbywać się będzie podczas zamykania programu, więc w zdarzenie OnClose naszej formy, wpiszemy ponownie kod z Listingu 5. który tym razem jest opisany jako: {Procedura OnClose}. Główne zdarzenia zostały już napisane, czas teraz na kod, który umożliwi odbieranie danych. Klikamy na komponent IdHTTPServer1 i przechodzimy do Object Inspector. Odszukujemy Event podpisany jako OnCommand- Get i wpisujemy kod z Listingu 6. Aplikacja jest już gotowa, możemy ją zapisać i skompilować. Podobnie jak wcześniej, stworzymy jeszcze plik PHP, z tym, że teraz to on będzie wysyłał dane do serwera. W tym celu posłużymy się najprostszym formularzem, którego propozycja kodu znajduje się na Listingu 7. Pamiętajmy, że nasz program działa na porcie 8008 (aby uniknąć kolizji z innymi aplikacjami), więc będziemy odwoływać się do niego w następujący sposób http://adresip:numerportu, czyli http:// localhost:8008. Teraz musimy jeszcze uruchomić nasz program i wypełnić pola formularza, a następnie go zatwierdzić. Wynik działania aplikacji, która odebrała parametry i zwróciła wynik do przeglądarki znajduje się na Rysunku 6. Przykładowe zastosowanie Powyższy sposób komunikacji może być wykorzystany- np. do zdalnej administracji komputerem poprzez stronę WWW. Wg definicji serwera aplikacji jest to program działający na zdalnej maszynie obsługujący żądania kierowane do aplikacji, do której dostęp zapewnia. Użytkownik łączy się za pośrednictwem przeglądarki internetowej, kieruje żądanie do wybranej aplikacji, a całość operacji odbywa się po stronie komputera należącego do organizacji, która udostępnia daną aplikację. (pl.wikipedia.org).zastanówmy się najpierw nad ogólną koncepcją naszej aplikacji zarówno klienta jak i serwera. Klient w tym przypadku strona WWW powinien zawierać pola: Login oraz Hasło, aby naszego zdalnego systemu nie mogły przechwycić Kodowanie danych Jeżeli chcemy wysłać dane do serwera zawierające elementy formularza, to muszą one wcześniej zostać zakodowane. Kodowanie application/x-www-form-urlencoded jest kodowaniem domyślnym, które przeglądarka wysyła do skryptu; używamy go do wysyłania małych ilości informacji. Nie sprawdza się ono jednak podczas wysyłania dużych ilości danych binarnych lub rozległego tekstu. Do tego celu należy użyć kodowania multipart/form-data. Niezależnie od tego, na jakie kodowanie się zdecydujemy, musimy pamiętać o jego implementacji w naszym programie. 28 06/2007
Delphi i PHP niepowołane osoby, a także kilka opcji wyboru, które będą reprezentowały możliwe do wykonania funkcje przez zdalny system. Po wybraniu opcji Wyślij, skrypt wyśle parametry do aplikacji serwera. Serwer natomiast, po odbiorze porcji danych, powinien mieć możliwość ich walidacji, w tym danych użytkownika (jego nazwy oraz hasła), który chce skorzystać z jego usług, a także oczywiście mieć możliwość wykonania żądanego polecenia. Po jego wykonaniu serwer powinien zwrócić wynik, następnie wszystkie operacje powinny być zapisywane w logach. Listing 6. Odbieranie danych przez aplikację procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); StrResult: String; {w Memo zostaną umieszczone żądania (request server)} Memo1.Lines.Add(ARequestInfo.Document); {tworzymy kod wynikowy dla przeglądarki} StrResult := '<h1>testowa strona serwera.</h1>' +'<b>odebrane dane:</b>' {ARequestInfo.Host zawiera informacje o hoście użytkownika...} + '<p>host: ' + ARequestInfo.Host + '</p>' {...natomiast ARequestInfo.UnparsedParams wyświetla parametry przekazane do naszego serwera} + '<p>parametry: ' + ARequestInfo.UnparsedParams + '</p>'; {wyświetlamy w przeglądarce stworzony łańcuch} AResponseInfo.ContentText := StrResult; {do Edit1 dodajemy parametry przekazane do serwera} Edit1.Text:=ARequestInfo.UnparsedParams; Listing 7. Formularz wysyłający zmienne metodą POST do aplikacji Rysunek 4. Gotowa aplikacja wysyłająca obrazy na hosting ImageShack.us i parsująca kod wynikowy Rysunek 5. Przykładowe rozmieszczenie komponentów. Z góry: komponenty ze standardowymi właściwościami Caption, natomiast u dołu: komponenty posiadające zmodyfikowaną tą właściwość Rysunek 6. Gotowa aplikacja odbierająca dane od skryptu Rysunek 7. Zarządzanie komputerem z poziomu przeglądarki <html> <!--implementacja formularza: odwołuje się on do http://localhost:8008 i przesyła dane metodą POST --> <form action="http://localhost:8008" method="post"> <label for="login">login: </label> <input name="login" type="text"> <label for="password">password: </label> <input name="password" type="password"> <input value="go!" type="submit"> </form> </html> Listing 8. Przykładowa aplikacja klienta <html> <form action="http://localhost:8008" method="post"> <label for="login"><b>login</b></label> <input name="login" type="text" value="" style="width: 300px" /> <label for="pass"><b>hasło</b></label> <input name="pass" type="text" value="" style="width: 300px" /> <label><b>polecenie</b></label> <input type="radio" name="opcje" value="shutdown" checked/>wyłącz zdalny komputer <input type="radio" name="opcje" value="message"/>wyślij wiadomość: <textarea name="wiadomość" style="width: 300px; height: 150px"></textarea> <input type="submit" name="submit" value="wyślij"> </form> </html> www.phpsolmag.org 29
Listing 9. Odbieranie danych przez aplikację i wykonywanie żądanych poleceń procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); StrResult: String; login,pass,autoryzacja: boolean; {w Memo zostaną umieszczone żądania (request server)} Memo1.Lines.Add(ARequestInfo.Document); {do logów dodajemy parametry przekazane do serwera} Memo1.Lines.Add('Odebrane parametry:'); Memo1.Lines.Add(ARequestInfo.Params.Text); {walidacja danych:} {czy login (parametr 0) jest poprawny?} if ARequestInfo.Params[0]='login=admin' then login:=true else login:=false; {czy hasło (parametr 1) jest poprawne?} if ARequestInfo.Params[1]='pass=password' then pass:=true else pass:=false; {identyfikacja} if (login and pass) then autoryzacja:=true else autoryzacja:=false; {wypisujemy kod wynikowy} StrResult := '<b>odebrane dane:</b>' + '<p>autoryzacja loginu: ' + BoolToStr(login,True) + '</p>' + '<p>autoryzacja hasła: ' + BoolToStr(pass,True) + '</p>' + '<p>wykonanie polecenia: ' + BoolToStr(autoryzacja,True) + '</p>'; AResponseInfo.ContentText := StrResult; {zwracamy informację do przeglądarki} AResponseInfo.WriteContent; {sprawdzenie polecenia} if ((ARequestInfo.Params[2]='opcje=message') and autoryzacja) then ShowMessage(StringReplace(ARequestInfo.Params[3],'wiadomość=','',[])) else if ((ARequestInfo.Params[2]='opcje=shutdown') and autoryzacja) then {wywołanie cmd.exe z parametrem shutdown- zamykamy system} ShellExecute(Handle,'open','cmd.exe','/c shutdown /s','c:',sw_hide) else Projekt klienta Pierwszym krokiem będzie stworzenie strony WWW, której funkcją będzie wysyłanie pakietów z danymi do naszej aplikacji. Opierać się ona będzie na prostym formularzu, którego kod został zaprezentowany na Listingu 8. Podobnie jak poprzednio, będziemy korzystać z portu numer 8008. Jeżeli zamierzamy korzystać z aplikacji, z innego komputera niż localhost, niekiedy wymagane będzie odblokowanie tego portu na firewall u. Przyjrzyjmy się Listingowi 8. Jak widać, zostały w nim zawarte planowane pola: Login oraz Hasło, a także pola typu Radio, które reprezentują możliwe do wykonania zadania. Dla przykładu umieściłem dwa: możliwość wyłączenia zdalnego komputera, a także opcję wyświetlenia na nim wiadomości. Skoro ten etap mamy już za sobą możemy przejść do projektowania serwera. Projekt serwera Projekt serwera w dużej mierze opierać będzie się na aplikacji zaprezentowanej na początku obecnego rozdziału. Śmiało możemy otworzyć tamten projekt i zacząć go modyfikować. Zacznijmy od usunięcia Label-u podpisanego jako Odebrane parametry, a także komponentu Edit1. Nie będą one nam już potrzebne. Następnie, tak jak poprzednio, klikamy na komponent IdHTTPServer1 i przechodzimy do Object Inspector a. Odszukujemy Event podpisany jako OnCommandGet i wpisujemy kod z Listingu 9. Pozostałe procedury projektu pozostawiamy bez zmian. Procedura OnCommandGet, którą przed chwilą wpisaliśmy, zawiera przykładowe polecenie zamykające system- w tym wypadku posłużyliśmy się funkcją ShellExecute, tak W Sieci http://www.codegear.com/downloads/ free/delphi http://www.indyproject.org/ https://addons.mozilla.org/pl/firefox/ addon/60 http://www.4programmers.net więc do sekcji Uses musimy dodać moduł ShellAPI. Sam kod nie jest trudny do zrozumienia, został on oczywiście opatrzony komentarzami, dlatego zachęcam do jego przejrzenia. Przyjrzyjmy się fragmentowi odpowiedzialnemu za walidację danych. Zmienna ARequestInfo.Params[index] zawiera kolejne parametry, do których możemy odwoływać się po ich indeksie. Nasz skrypt, który pisaliśmy wcześniej, wysyła kolejno porcje danych: login, będący parametrem zerowym, hasło, będące parametrem pierwszym, i tak dalej. Ważna jest kolejność, jaką zachowujemy. Wysyłając najpierw hasło, a dopiero potem login do serwera- automatycznie zmieniamy ich indeksy, co powoduje, że dane nie przechodzą walidacji. Możemy oczywiście skorzystać na przykład z instrukcji for...to i sprawdzać po kolei wszystkie odebrane skrypty, jednakże, przy tak małej ilości danych nie jest to konieczne. Wynik działania Proces tworzenia aplikacji, zarówno serwera jak i klienta, mamy już za sobą. Teraz nadszedł czas na przyjrzenie się rezultatom ich działania. Tak więc: uruchamiamy nasze aplikacje- serwer i stronę z formularzem. Wypełniamy podane pola (niekoniecznie poprawnie, aby zobaczyć, czy wszystko działa tak jak należy) i wysyłamy dane. Przykład działania aplikacji pokazany jest na Rysunku 7. Podsumowanie Mój niewielki artykuł opisuje tylko wierzchołek góry lodowej, jaką jest pakiet INDY. Dla jego potrzeb wykorzystałem zaledwie dwa komponenty z ponad ich stuelementowej kolekcji. Pamiętaj, że teoria śni, praktyka uczy. W momencie, gdy sam stworzysz i przeanalizujesz aplikacje, zmodyfikujesz ich kody i prześledzisz działanie, będziesz mógł w pełni zrozumieć zagadnienia, które nimi kierują. ARTUR CHUDZIK Autor w wolnych chwilach zajmuje się programowaniem, a także administracją portalem WebHat.pl, którego jest założycielem. Obecnie uczy się w drugiej klasie 1LO w Łańcucie. Kontakt z autorem: admin@webhat.pl 30 06/2007