Technika 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. Jak pamiętamy, dane przesyłane metodą POST Dowiesz się... Powinieneś wiedzieć... mają postać: " W jaki sposób odebrać dane ze skryptu. " Powinieneś znać podstawy języka i środowi- " W jaki sposób wysłać dane do skryptu. ska Delphi. pole1=wartość&pole2=wartość2. " Powinieneś znać podstawy języka PHP i HTML. " Powinieneś znać podstawowe pojęcia związa- Dlatego musimy je przesłać w taki sposób. ne z protokołem http. Nazwy pól: text1 i text2 są nazwami przy- kładowymi, które następnie zastosujemy w skrypcie odbierającym dane. Należy pamię- niśmy im nadać, zostały zaprezentowane na tać, że będą one różne dla każdej strony, która Rysunku 1. odbiera pakiety z danymi. Poziom trudności Po umieszczeniu wszystkich elementów, Powyższa linijka tworzy ciąg powstały z po- kliknijmy tylko raz na komponent IdHTTP łączenia tekstu w cudzysłowie, z tekstem wpi- i przejdzmy do Object Inspector a, a następ- sanym w Edit2 i Edit3. Owy ciąg jest następ- nie odszukajmy listę Request. Po jej rozwinię- nie wysyłany do skryptu za pomocą funkcji posób komunikacji, opiera się na meto- ciu ukaże się nam kilka atrybutów, interesować POST komponentu IdHTTP. Ostatnim kro- dzie POST, która umożliwia wysyłanie nas będzie ten o nazwie ContentType. W to pole kiem będzie zapisanie i skompilowanie nasze- Sdanych przy pomocy nagłówków HTTP. musimy wpisać ciąg: go projektu. Dane przesyłane tą metodą mają postać: Gdy mamy już gotową aplikację, może- application/x-www-form-urlencoded. my stworzyć najprostszy plik PHP, który po- pole1=wartość&pole2=wartość2. służy nam do jej testowania. Propozycja ko- Więcej o kodowaniu danych możemy przeczy- du znajduje się na Listingu 3. W skrypcie, Tak więc: nazwa pola jest oddzielona od jego tać w ramce Kodowanie danych . dla ułatwienia, zostały zastosowane zmien- wartości znakiem = ne o takich samych nazwach, jak w naszym , natomiast poszczególne porcje danych oddziela znak & programie. . Przykładowy Pisanie kodu nagłówek z powyższymi informacjami wyglą- Projektowanie aplikacji mamy już za sobą, czas Po zapisaniu pliku PHP i umieszczeniu go dałby więc tak, jak na Listingu 1. więc na napisanie kilku linijek kodu. Nasz pro- na serwerze, uruchamiamy naszą aplikację, po- gram będzie miał za zadanie: dajemy odpowiednie parametry i naciskamy Z Delphi do PHP przycisk POST. Pole tekstowe Memo podpisa- Zacznijmy projektować naszą aplikację, która " pobrać adres strony; ne jako Kod wynikowy zostanie automatycz- prześle dane do skryptu metodą POST . Uru- " pobrać dwie zmienne do wysłania; nie uzupełnione danymi zwróconymi przez chamiamy Delphi i z menu File wybieramy " wysłać dane; skrypt. Przedstawia to Rysunek 2. New Application. " odebrać kod wynikowy. Po instalacji pakietu INDY w Delphi, przy- Listing 1. Przykładowy nagłówek z danymi będzie nam kilka nowych zakładek na pa- Jego lista zadań jest krótka. Dzięki zastoso- wysłanymi metodą POST lecie komponentów. Odszukajmy zakład- waniu komponentu IdHTTP, kod również bę- kę Indy Clients i wybierzmy z niej kompo- dzie krótki. Kliknijmy dwa razy na Button ie POST /index.php HTTP/1.1 nent IdHTTP. To on umożliwi nam wysyła- i wpiszmy kod, który znajduje się na Listingu 2. Host: www.domena.com nie danych. Przyjrzyjmy się linijce odpowiadającej za User-Agent: Mozilla/5.0 Umieścimy jeszcze następujące kompo- utworzenie strumienia StreamIn, a szczególnie Content-Length: 28 nenty: 3x Edit, 4x Label, 1x Button, oraz 1x fragmentowi: Content-Type: application/x-www-form- Memo (wszystkie one znajdują się w zakład- urlencoded ce Standard). Ich przykładowe rozmieszcze- (Format('text1=%s&text2=%s', [Edit2.Text,Ed pole1=wartość&pole2=wartość2 nie, a także właściwości Caption jakie powin- it3.Text])). 26 06/2007 Delphi i PHP Przykładowe zastosowanie Listing 2. Wysyłanie danych z aplikacji do skryptu metodą POST Przykładowym zastosowaniem powyższej procedure TForm1.Button1Click(Sender: TObject); metody komunikacji może być program, któ- var ry będzie uploadował wybrane obrazy z na- StreamIn,StreamOut: TStringStream; szego dysku twardego na darmowy hosting begin zdjęć ImageShack (dostępny pod adresem try www.imageshack.us). Zaczniemy od małego {Tworzymy strumień StreamIn zawierający łańcuch danych, rekonesansu, tak więc otwieramy ową stro- zostanie on potem wysłany do skryptu} nę. Aby uniknąć żmudnej analizy kodu w StreamIn := TStringStream.Create(Format('text1=%s&text2=%s', [Edit2.Text,Edit3.Text] celu wydobycia szczegółów, skorzystajmy )); z wtyczki do Firefox a pod nazwą Web Develo- {Tworzymy pusty strumień StreamOut. Będzie on odpowiedzialny per (link znajduje się w ramce W Sieci ). Po- za odbiór kodu wynikowego skryptu} siada ona funkcję, która modyfikuje wyświe- StreamOut := TStringStream.Create(''); tlaną stronę pokazując, na przykład, szcze- {Procedura POST komponentu IdHTTP i jej kolejne parametry: góły formularzy. Zostało to pokazane na Ry- Adres URL, strumień z danymi, strumień odbierający} sunku 3. IdHTTP1.Post(Edit1.Text,StreamIn,StreamOut); Co na nim widzimy? Po pierwsze for- {Wyświetlamy kod wynikowy ze strumienia w Memo} mularz odwołuje się do strony głównej Ima- Memo1.Lines.Text:=StreamOut.DataString; geShack.us (fragment form action= ), po finally drugie- format kodowania danych to multipart/ {Zwalniamy strumienie} form-data (fragment enctype= ), i wreszcie po StreamIn.Free; trzecie- pole, z którego pobierana jest ścież- StreamOut.Free; ka do wysyłanego pliku (input name="fileuplo- end; ad ). Posiadając te informacje możemy rozpo- end; cząć projektowanie aplikacji. Listing 3. Odbieranie danych z aplikacji wysłanych metodą POST Przykładowa aplikacja $text1=$_POST['text1']; tu. Potrzebne nam będą komponenty: 1x $text2=$_POST['text2']; Edit, 1x Memo, 1x button, a także komponent echo "Zmienna \"text1\" to: $text1\n"; //wyświetlamy zmienną text1 IdHTTP. echo "Zmienna \"text2\" to: $text2\n"; //wyświetlamy zmienną text2 Podobnie jak ostatnio kliknijmy raz na ?> komponent IdHTTP i przejdzmy do Object Inspector a , a następnie odszukajmy li- stę Request. Po jej rozwinięciu ukaże nam się kilka atrybutów; interesować nas będzie ten o nazwie ContentType. W to pole musimy wpi- sać ciąg: multipart/form-data. Teraz, kliknijmy dwa razy na komponent But- ton, i w edytorze kodu wpiszmy fragment z Listingu 4. Jak widać została tam zawarta zmienna MyData typu TIdMultiPartFormDa- taStream. Jest to specyficzny typ strumienia Rysunek 3. Wtyczka Web Developer wyświetlająca szczegóły formularza na stronie ImageShack.us Rysunek 1. Przykładowe rozmieszczenie komponentów. Z lewej strony komponenty ze standardowymi Rysunek 2. Gotowa aplikacja wysyłająca dane do właściwościami Caption, natomiast z prawej komponenty posiadające zmodyfikowaną tę właściwość skryptu 06/2007 www.phpsolmag.org 27 Technika z danymi, którego implementacja konieczna oraz 2x Memo (wszystkie one znajdują się w OnClose naszej formy, wpiszemy ponownie jest do wysłania danych, zakodowanych w for- zakładce Standard). Ostatnio użyliśmy kom- kod z Listingu 5. który tym razem jest opisa- macie multipart/form-data. Aby z niego skorzy- ponentu, który był klientem protokołu HTTP. ny jako: stać, konieczne jest dodanie do sekcji Uses mo- Pisząc tę aplikację, będziemy musieli stwo- dułu IdMultipartFormData. Jest on dołączony rzyć serwer HTTP. Odszukajmy więc zakład- {Procedura OnClose}. do pakietu INDY. Po kompilacji projektu na- kę Indy Servers i wybierzmy z niej komponent leży w pole Edit wpisać ścieżkę obrazu, któ- IdHTTPServer. Ich przykładowe rozmieszcze- Główne zdarzenia zostały już napisane, czas ry chcemy uploadować, a następnie nacisnąć nie, a także właściwości Caption jakie powin- teraz na kod, który umożliwi odbieranie da- przycisk Button. niśmy im nadać, zostały zaprezentowane na nych. Klikamy na komponent IdHTTPServer1 Zachęcam do przejrzenia kodów zródło- Rysunku 5. i przechodzimy do Object Inspector. Odszu- wych, które zostały dołączone do płyty. Do kujemy Event podpisany jako OnCommand- aplikacji dodałem również parser, który po- Pisanie kodu Get i wpisujemy kod z Listingu 6. biera z kodu wynikowego linki do naszego Przyszedł czas na oprogramowanie naszej apli- Aplikacja jest już gotowa, możemy ją zapi- obrazu, ale to temat na inny artykuł. Przy- kacji. Zacznijmy od aktywacji serwera- klikamy sać i skompilować. Podobnie jak wcześniej, kład działania aplikacji pokazany jest na Ry- na formie i odszukujemy właściwość OnCre- stworzymy jeszcze plik PHP, z tym, że te- sunku 4. ate na zakładce Events w oknie Object Inspector raz to on będzie wysyłał dane do serwera. i wpisujemy tam fragment kodu z Listingu 5. W tym celu posłużymy się najprostszym for- Z PHP do Delphi... podpisanego jako: mularzem, którego propozycja kodu znajdu- Teraz nadszedł czas na napisanie programu, je się na Listingu 7. Pamiętajmy, że nasz pro- który będzie umożliwiał odbieranie zmien- {Procedura OnCreate}. gram działa na porcie 8008 (aby uniknąć ko- nych wysyłanych przez skrypt. lizji z innymi aplikacjami), więc będziemy Stworzymy nową aplikację i na formie Deaktywacja serwera odbywać się będzie pod- odwoływać się do niego w następujący spo- umieścimy komponenty: 2x Label, 1x Edit czas zamykania programu, więc w zdarzenie sób http://AdresIP:NumerPortu, czyli http:// localhost:8008. Teraz musimy jeszcze uruchomić nasz pro- Listing 4. Wysyłanie danych z aplikacji do strony ImageShack gram i wypełnić pola formularza, a następnie procedure TForm1.Button1Click(Sender: TObject); go zatwierdzić. Wynik działania aplikacji, która var odebrała parametry i zwróciła wynik do prze- MyData: TIdMultiPartFormDataStream; glądarki znajduje się na Rysunku 6. begin {tworzymy specyficzny strumień z danymi} Przykładowe zastosowanie MyData:=TIdMultiPartFormDataStream.Create; Powyższy sposób komunikacji może być wyko- try rzystany- np. do zdalnej administracji kompu- {kolejny krok to dodanie do strumienia, metodą AddFile, kolejno: terem poprzez stronę WWW. nazwy pola (fileupload),ścieżki pliku, a także typu zawartości Wg definicji serwera aplikacji jest to pro- pakietu z danymi(file)} gram działający na zdalnej maszynie obsługu- MyData.AddFile('fileupload',Edit1.Text,'file'); jący żądania kierowane do aplikacji, do której {kod wynikowy zostanie wyświetlony w Memo1} dostęp zapewnia. Użytkownik łączy się za po- Memo1.Lines.Text:=IdHTTP1.Post('http://imageshack.us/',MyData); średnictwem przeglądarki internetowej, kieru- finally je żądanie do wybranej aplikacji, a całość ope- {Zwalniamy strumień} racji odbywa się po stronie komputera nale- MyData.Free; żącego do organizacji, która udostępnia daną end; aplikację. (pl.wikipedia.org).Zastanówmy się naj- end; pierw nad ogólną koncepcją naszej aplika- cji zarówno klienta jak i serwera. Klient Listing 5. Aktywacja i deaktywacja serwera IdHTTPServer1 w tym przypadku strona WWW powinien {Procedura OnCreate} zawierać pola: Login oraz Hasło , aby nasze- procedure TForm1.FormCreate(Sender: TObject); go zdalnego systemu nie mogły przechwycić begin {Ustawiamy numer portu dla serwera} IdHTTPServer1.DefaultPort := 8008; Kodowanie danych Jeżeli chcemy wysłać dane do serwera za- {aktywujemy serwer} wierające elementy formularza, to muszą IdHTTPServer1.Active:=True; one wcześniej zostać zakodowane. Kodo- end; wanie application/x-www-form-urlencoded {- - -} jest kodowaniem domyślnym, które prze- {procedura OnClose} glądarka wysyła do skryptu; używamy go do wysyłania małych ilości informacji. Nie procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); sprawdza się ono jednak podczas wysyła- begin nia dużych ilości danych binarnych lub roz- {dezaktywujemy serwer} ległego tekstu. Do tego celu należy użyć ko- IdHTTPServer1.Active:=False; dowania multipart/form-data. end; Niezależnie od tego, na jakie kodowa- {- - -} nie się zdecydujemy, musimy pamiętać o je- go implementacji w naszym programie. 28 06/2007 Delphi i PHP niepowołane osoby, a także kilka opcji wybo- Listing 6. Odbieranie danych przez aplikację ru, które będą reprezentowały możliwe do wy- konania funkcje przez zdalny system. Po wy- procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread; braniu opcji Wyślij , skrypt wyśle parametry ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); do aplikacji serwera. var Serwer natomiast, po odbiorze porcji da- StrResult: String; nych, powinien mieć możliwość ich wali- begin dacji, w tym danych użytkownika (jego na- {w Memo zostaną umieszczone żądania (request server)} zwy oraz hasła), który chce skorzystać z jego Memo1.Lines.Add(ARequestInfo.Document); usług, a także oczywiście mieć możliwość wy- {tworzymy kod wynikowy dla przeglądarki} konania żądanego polecenia. Po jego wykona- StrResult := '
Testowa strona serwera.
' niu serwer powinien zwrócić wynik, następ- +'Odebrane dane:' nie wszystkie operacje powinny być zapisywa- {ARequestInfo.Host zawiera informacje o hoście użytkownika...} ne w logach. + '
Rysunek 7. Zarządzanie komputerem z poziomu przeglądarki 06/2007 www.phpsolmag.org 29 Technika Listing 9. Odbieranie danych przez aplikację i wykonywanie żądanych poleceń W Sieci procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread; " http://www.codegear.com/downloads/ ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); free/delphi var " http://www.indyproject.org/ StrResult: String; " https://addons.mozilla.org/pl/firefox/ login,pass,autoryzacja: boolean; addon/60 begin " http://www.4programmers.net {w Memo zostaną umieszczone żądania (request server)} Memo1.Lines.Add(ARequestInfo.Document); {do logów dodajemy parametry przekazane do serwera} więc do sekcji Uses musimy dodać moduł Memo1.Lines.Add('Odebrane parametry:'); ShellAPI. Memo1.Lines.Add(ARequestInfo.Params.Text); Sam kod nie jest trudny do zrozumienia, zo- {walidacja danych:} stał on oczywiście opatrzony komentarzami, {czy login (parametr 0) jest poprawny?} dlatego zachęcam do jego przejrzenia. Przyj- if ARequestInfo.Params[0]='login=admin' rzyjmy się fragmentowi odpowiedzialnemu za then login:=True else login:=False; walidację danych. {czy hasło (parametr 1) jest poprawne?} Zmienna ARequestInfo.Params[index] zawie- if ARequestInfo.Params[1]='pass=password' ra kolejne parametry, do których możemy od- then pass:=True else pass:=False; woływać się po ich indeksie. Nasz skrypt, który {identyfikacja} pisaliśmy wcześniej, wysyła kolejno porcje da- if (login and pass) then autoryzacja:=True nych: login, będący parametrem zerowym, ha- else autoryzacja:=False; sło, będące parametrem pierwszym, i tak da- {wypisujemy kod wynikowy} lej. Ważna jest kolejność, jaką zachowujemy. StrResult := 'Odebrane dane:' Wysyłając najpierw hasło, a dopiero potem lo- + '
Autoryzacja loginu: ' + BoolToStr(login,True) + '
' gin do serwera- automatycznie zmieniamy ich + '
Autoryzacja hasła: ' + BoolToStr(pass,True) + '
' indeksy, co powoduje, że dane nie przechodzą + '
'; walidacji. AResponseInfo.ContentText := StrResult; Możemy oczywiście skorzystać na przykład z {zwracamy informację do przeglądarki} instrukcji for...to i sprawdzać po kolei wszystkie AResponseInfo.WriteContent; odebrane skrypty, jednakże, przy tak małej ilo- {sprawdzenie polecenia} ści danych nie jest to konieczne. if ((ARequestInfo.Params[2]='opcje=message') and autoryzacja) then Wynik działania ShowMessage(StringReplace(ARequestInfo.Params[3],'wiadomość=','',[])) Proces tworzenia aplikacji, zarówno serwe- else ra jak i klienta, mamy już za sobą. Teraz nad- if ((ARequestInfo.Params[2]='opcje=shutdown') and autoryzacja) szedł czas na przyjrzenie się rezultatom ich then działania. {wywołanie cmd.exe z parametrem shutdown- zamykamy system} Tak więc: uruchamiamy nasze aplikacje- ser- ShellExecute(Handle,'open','cmd.exe','/c shutdown /s','C:',SW_HIDE) wer i stronę z formularzem. Wypełniamy poda- else ne pola (niekoniecznie poprawnie, aby zoba- end; czyć, czy wszystko działa tak jak należy) i wysy- łamy dane. Przykład działania aplikacji pokaza- ny jest na Rysunku 7. Projekt klienta Projekt serwera Pierwszym krokiem będzie stworzenie stro- Projekt serwera w dużej mierze opierać bę- Podsumowanie ny WWW, której funkcją będzie wysyłanie pa- dzie się na aplikacji zaprezentowanej na po- Mój niewielki artykuł opisuje tylko wierz- kietów z danymi do naszej aplikacji. Opierać czątku obecnego rozdziału. Śmiało możemy chołek góry lodowej, jaką jest pakiet INDY. się ona będzie na prostym formularzu, które- otworzyć tamten projekt i zacząć go mody- Dla jego potrzeb wykorzystałem zaledwie go kod został zaprezentowany na Listingu 8. fikować. dwa komponenty z ponad ich stuelemento- Podobnie jak poprzednio, będziemy korzystać Zacznijmy od usunięcia Label-u podpi- wej kolekcji. Pamiętaj, że teoria śni, prakty- z portu numer 8008. Jeżeli zamierzamy korzy- sanego jako Odebrane parametry , a także ka uczy . W momencie, gdy sam stworzysz stać z aplikacji, z innego komputera niż local- komponentu Edit1. Nie będą one nam już i przeanalizujesz aplikacje, zmodyfikujesz host, niekiedy wymagane będzie odblokowanie potrzebne. ich kody i prześledzisz działanie, będziesz tego portu na firewall u. Następnie, tak jak poprzednio, klikamy na mógł w pełni zrozumieć zagadnienia, które Przyjrzyjmy się Listingowi 8. Jak widać, zo- komponent IdHTTPServer1 i przechodzimy do nimi kierują. stały w nim zawarte planowane pola: Login oraz Object Inspector a. Odszukujemy Event pod- Hasło, a także pola typu Radio , które repre- pisany jako OnCommandGet i wpisujemy kod zentują możliwe do wykonania zadania. Dla z Listingu 9. Pozostałe procedury projektu po- ARTUR CHUDZIK przykładu umieściłem dwa: możliwość wyłą- zostawiamy bez zmian. Autor w wolnych chwilach zajmuje się progra- czenia zdalnego komputera, a także opcję wy- Procedura OnCommandGet, którą przed mowaniem, a także administracją portalem We- świetlenia na nim wiadomości. Skoro ten etap chwilą wpisaliśmy, zawiera przykładowe po- bHat.pl, którego jest założycielem. Obecnie uczy się mamy już za sobą możemy przejść do projekto- lecenie zamykające system- w tym wypad- w drugiej klasie 1LO w Aańcucie. wania serwera. ku posłużyliśmy się funkcją ShellExecute, tak Kontakt z autorem: admin@webhat.pl 30 06/2007