HTTP

Adam Boduch

Tematem tego artykułu będą sposoby na przesłanie danych do skryptu z poziomu Delphi. Nasz program napisany w Delphi będzie symulował formularz HTML i będzie przesyłał dane do skryptu. Tak samo jak nasz program, który za chwilę napiszemy działają wszelkie programy do wysyłania SMS. Otóż łączą się one z bramką operatora i wysyłają wiadomość tekstową. Inaczej mówiąc symulują formularz HTML. My w naszym przykładzie skorzystamy z innego przykładu.

Otóż 18 lutego w serwisie 4programmers.net powstało forum dyskusyjne. Napiszemy teraz program, który połączy się z forum i wyśle nowy post na forum. Żeby napisać taki program trzeba przeanalizować kod HTML strony z formularzem, by poznać jakie pola przez formularz są wymagane. Ja w swoim przykładzie będę korzystał z Delphi 5 i pakietu Indy. Pakiet ten możesz ściągnąć z tej strony z działu Kody źródłowe. Dane do formularza przesyłane będą za pomocą metody POST (możliwa jest jeszcze metoda GET).

Nie wiem, czy wiesz, jaki jest sposób przesyłania danych z formularza do skryptu. Możesz o tym np. poczytać w dziale CGI lub PHP. Otóż każde pole formularza jest oddzielone od siebie znakiem &, a nazwa pola formularza i jego wartość jest dzielona znakiem =. Tak więc dane przesyłane do skryptu mogą być takiej postaci:

title=tytul&autor=Bald

Formularz do wysłania nowego postu z forum wymaga akurat podania xywki danej osoby, jej adresu e-mail, tematu postu oraz treści wiadomości. Umieść na formie komponent IdHTTP z pakietu Indy i pierwsze co musisz zrobić to ustawić typ danych. Otóż przeglądarka przesyła do skryptu nagłówek w postaci typu danych. Może to być np. text/html (pliki HTML), text/plain (czysty tekst). Ty wpisz we właściwości Content-type w Inspektorze Obiektów:

application/x-www-form-urlencoded
Informuje o tym, że dane będą przesyłane z formularza. Ustaw także właściwość Host na 'http://4programmers.net'.

Na formularzu umieściłem parę komponentów, które są wymagane przez skrypt, czyli tytuł wiadomości (edtTitle), autor (edtNick), adres e-mail (edtMail) oraz treść wiadomości, czyli komponent TMemo (memBody).

Do wysłania danych z programu do skryptu potrzebna nam będzie tylko jedna procedura:

procedure TMainForm.btnSendClick(Sender: TObject);
var
  Input, OutPut : TStringStream;
  ErrCode : String;
  BeginPos : Integer;
begin
{ stworzenie strumieni tekstowych }
  Input := TStringStream.Create('');
  Output := TStringStream.Create('');
  try
    btnSend.Enabled := False; // zablokowanie przycisku
  { dodanie do strumienia lancucha oznaczajcego dane nowego tematu na forum. Dane do tego
    lancucha pobierane sa z komponentow }
    Input.WriteString(Format('title=%s&name=%s&email=%s&body=%s', [edtTitle.Text, edtNick.Text, 
edtmail.Text, memBody.Lines.Text]));
    Forum.Post('http://4programmers.net/forum/message.php?action=create', // wysłanie danych 
ze strumienia do skryptu
      Input, Output);

    { strumień Output przechwytuje odpowiedz serwera, czyli kod HTML jaki wyświetlił skrypt }

    if Pos('<title>Bł&plusmn;d</title>', Output.DataString) <> 0 then  // jeżeli wystąpi taka linia
    begin
    { to znaczy, ze wystapil blad. Zadaniem ponizszych komend jest wyluskanie bledu
      z kodu HTML i wyswietlenie samego bledu. Na samym jednak poczatku nalezy pobirac
      polozenie znacznika </p> od ktoego to zaczyna sie wlasciwy komunikat bledu }
      BeginPos := Pos('</p>', Output.DataString)+4;
    { nastepnie nalezy pobrac koniec komunikatu bledu. W tym celu znajdujemy znacznik </center>
      ktory oznacza koniec komunikatu bledu. Odejmujemy jego pozycje od zmiennej BeginPos i 
uzyskujemy
      w ten sposob polozenie koncowe komunikatu bledu }
      ErrCode := Copy(Output.DataString, BeginPos, Pos('</center>', Output.DataString) - BeginPos);
    { teraz pozostaje juz tylko ten komunikat wyswietlic }
      MessageBox(Handle, PChar(Errcode), 'Wystąpił błąd podczas próbie wysłania postu...', MB_OK +
 MB_ICONWARNING);
    end else Messagebox(Handle, 'Dziękuje za wysłanie posta na forum dyskusjne 4programmers.net! 
Wejdź na stronę www.4programmers.net, aby zobaczyć swój post!',
    'Dziękujemy...', MB_OK + MB_ICONINFORMATION);
  finally
  { zwolnienie zasobów }
    Input.Free;
    Output.Free;
    btnSend.Enabled := True;
  end;
end;

Kluczową w tej procedurze jest linia:

Forum.Post('http://4programmers.net/forum/message.php?action=create', // wysłanie danych 
ze strumienia do skryptu
      Input, Output);

To ona powoduje przesłanie danych do skryptu. "Forum" to oczywiście komponent IdHTTP. Pierwszy parametr procedury Post to URL pliku, do którego mają być przesłane dane. Drugi i trzeci parametr to zmienne typu TStringStream. Ostatni to rezultat wykonania polecenia, czyli kod HTML jaki zostanie zwrócony przez skrypt. Drugi od końca natomiast zawiera dane, które mają być przesłane do skryptu. Te dane są pobierane z komponentów w ten sposób:

Input.WriteString(Format('title=%s&name=%s&email=%s&body=%s', [edtTitle.Text, edtNick.Text,
edtmail.Text, memBody.Lines.Text]));
Procedura WriteString powoduje dodanie do strumienia nowego tekstu. Ten tekst jest zapisany w takiej formie w jakiej formularz przesyła dane do skryptu. Tak więc strumienie danych płyną do skryptu. A co dalej?

Później następuje sprawdzenie, czy operacja się powiodła, czy też nie. Skrypt na forum w razie błędu wyświetla kod HTML z informacją o błędzie. Także za pomocą polecenia Pos następuje sprawdzenie, czy w kodzie HTML, który został wyświetlony po przesłaniu danych do skryptu nie występuje kod:

<title>Błąd</title>

Jeżeli taki kod się tam znajduje to znaczy, że coś poszło nie tak. Wtedy z kodu HTML następuje "wyciągnięcie" komunikatu błędu. Tutaj także trzeba się przyjrzeć kodowi jaki występuje podczas błędu. Ja wiem np. że komunikat błędu w kodzie zawarty jest pomiędzy znacznikami

i </span>. Pozostaje tylko wyciągnąć stamtąd tekst. A robię to w ten sposób:</p>
BeginPos := Pos('</p>', Output.DataString)+4;
ErrCode := Copy(Output.DataString, BeginPos, Pos('</center>', Output.DataString) - BeginPos);

Czyli za pomocą funkcji Pos i Copy. Najpierw pobieram pozycje znacznika

(to jest początek komunikatu błędu), a później w funkcji Copy odejmuje położenie znacznika </span> od zmiennej BeginPos i już mamy pozycje zakończenia komunikatu błędu. Teraz nie pozostaje nic innego jak wyświetlić zmienną Errcode. (BTW: o tych funkcjach możesz poczytać w artykule "Łańcuchy").</p>

Cały kod programu przedstawia się następująco:

(****************************************************************)
(*                                                              *)
(*              Copyright (c) 2002 by Adam Boduch               *)
(*                   http://4programmers.net                    *)
(*                    [email protected]                     *)
(*                                                              *)
(****************************************************************)

unit MainFrm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  IdBaseComponent, IdHTTP,
  StdCtrls, ComCtrls, IdTCPClient, IdTCPConnection, IdComponent;

type
  TMainForm = class(TForm)
    Forum: TIdHTTP;
    gbPost: TGroupBox;
    edtTitle: TEdit;
    lblTitle: TLabel;
    lblNick: TLabel;
    edtNick: TEdit;
    lblEmail: TLabel;
    edtMail: TEdit;
    memBody: TMemo;
    btnSend: TButton;
    StatusBar: TStatusBar;
    Label1: TLabel;
    procedure btnSendClick(Sender: TObject);
    procedure ForumConnected(Sender: TObject);
    procedure ForumWork(Sender: TObject; AWorkMode: TWorkMode;
      const AWorkCount: Integer);
    procedure ForumWorkEnd(Sender: TObject; AWorkMode: TWorkMode);
  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnSendClick(Sender: TObject);
var
  Input, OutPut : TStringStream;
  ErrCode : String;
  BeginPos : Integer;
begin
{ stworzenie strumieni tekstowych }
  Input := TStringStream.Create('');
  Output := TStringStream.Create('');
  try
    btnSend.Enabled := False; // zablokowanie przycisku
  { dodanie do strumienia lancucha oznaczajcego dane nowego tematu na forum. Dane do tego
    lancucha pobierane sa z komponentow }
    Input.WriteString(Format('title=%s&name=%s&email=%s&body=%s', [edtTitle.Text, edtNick.Text, 
edtmail.Text, memBody.Lines.Text]));
    Forum.Post('http://4programmers.net/forum/message.php?action=create', // wyslanie danych ze 
strumienia do skryptu
      Input, Output);

    { strumien Output przechwytuje odpowiedz serwera, czyli kod HTML jaki wyswietlil skrypt }

    if Pos('<title>Bł&plusmn;d</title>', Output.DataString) <> 0 then  // jezeli wystapi taka linia
    begin
    { to znaczy, ze wystapil blad. Zadaniem ponizszych komend jest wyluskanie bledu
      z kodu HTML i wyswietlenie samego bledu. Na samym jednak poczatku nalezy pobirac
      polozenie znacznika </p> od ktoego to zaczyna sie wlasciwy komunikat bledu }
      BeginPos := Pos('</p>', Output.DataString)+4;
    { nastepnie nalezy pobrac koniec komunikatu bledu. W tym celu znajdujemy znacznik </center>
      ktory oznacza koniec komunikatu bledu. Odejmujemy jego pozycje od zmiennej 
BeginPos i uzyskujemy
      w ten sposob polozenie koncowe komunikatu bledu }
      ErrCode := Copy(Output.DataString, BeginPos, Pos('</center>', Output.DataString) - BeginPos);
    { teraz pozostaje juz tylko ten komunikat wyswietlic }
      MessageBox(Handle, PChar(Errcode), 'Wystąpił błąd podczas próbie wysłania postu...', MB_OK + 
MB_ICONWARNING);
    end else Messagebox(Handle, 'Dziękuje za wysłanie posta na forum dyskusjne 4programmers.net! 
Wejdź na stronę www.4programmers.net, aby zobaczyć swój post!',
    'Dziękujemy...', MB_OK + MB_ICONINFORMATION);
  finally
  { zwolnienie zasobow }
    Input.Free;
    Output.Free;
    btnSend.Enabled := True;
  end;
end;

procedure TMainForm.ForumConnected(Sender: TObject);
begin
  StatusBar.SimpleText := 'Połączony...';
end;

procedure TMainForm.ForumWork(Sender: TObject; AWorkMode: TWorkMode;
  const AWorkCount: Integer);
begin
// pokazanie procesu przesylania danych
  StatusBar.SimpleText := 'Pracuje... ' + IntToStr(AWorkCount) + '%';
end;

procedure TMainForm.ForumWorkEnd(Sender: TObject; AWorkMode: TWorkMode);
begin
  StatusBar.SimpleText := 'Zakończono i rozłączono. Przesłanie danych do skryptu... ' + 
(Forum.ResponseText)
end;

end.

To chyba wszystko co miałem do przekazania na temat przesyłania danych do skryptu za pomocą metody POST. Istnieją także inne rozwiązani - np. metoda GET. Ja w tym przykładzie korzystałem z komponentów Indy, ale Ty możesz skorzystać z komponentów z zakładki FastNet.

Mam tylko prośbę, aby nie korzystać z programu, aby wysyłać tylko przykładowe posty i sprawdzić, czy program działa. Działa, działa, bo sprawdzałem...

6 komentarzy

bardzo dobry artykul. wielkie dzieki za napisanie jak odebrac wiadomosc z serwa (Output) tego potrzebowalem :)

Całkiem długi artykulik tu by się nie zmieścił:
user image

Link nie działa i w ogóle może i metoda dobra, ale mi nie działa... W Dziale Delphi jest mój post :
http://4programmers.net/Forum/viewtopic.php?id=56916&post=151837#151837

W tym arcie do strumienia wyszyłasz zmienne nie kodując ich w ogóle;
Z tego co wiem to zmienne należy kodować funkcją urlencode(s:string) {z PHP} czy to będzie działać bez kodowania??

Jeżeli używa sie metody get i jako parametr przesyłam łańcuch w postaci

getstr := 'http://szukaj.wp.pl/szukaj.html?szukaj='+s+
'&k=T&ns=T&p=T&a=N&sw=N&w=N&d=N&pk=N&n=N&e=N&pr=N&z=T&gl_wh=X&gl_ile=100'+
'&gl_o=T&gl_f=N&gl_sk=T&gl_pk=T&k_s=p&gl_j=&sw_m=all&w_s=255&pk_cs=0'+
'&pk_uq=0&pk_ld=&pk_lp=&pk_ds=&pk_de=&pk_mi=&pk_mx=&pk_sf=2&pk_st=1&pk_sr=0&zw=T';

gdzie S jest stringiem użytkownika
to jak przekodować go na HTML ?
Przypuścimy że wpisuje łańcuch "Władca pierścieni" to WP nie zwraca wyniku nie wie co to jest ść czy jest jakis gotowy moduł encodujacy