.
Worum geht es hier?
Die
Indy Komponenten bieten eine gute Möglichkeit um HTTP(S) Verbindungen zu ermöglichen. Wenn du also Informationen an eine Webseite senden oder empfangen willst bist du hier genau richtig.
Inhalt- HTTP Komponente
- Allgemeines
- Daten Empfangen
- Daten Senden
- Request Einstellungen
- Response Informationen
- Weitere Einstellungen
- Cookies
- Gzip
- SSL
- HTTP Proxy / SOCKS 4, 5
- Konklusion
- Tools
- WireShark
- FireFox Web Developer
1. HTTP Komponente
Allgemeines
Die Komponente findet ihr in der Tool-Palette unter "
Indy Clients". Dort klickt ihr auf TIdHTTP (Weltkugel) und zieht dies auf euer Formular. Delphi bindet nun automatisch eine Reihe von Units ein wichtig ist dabei IdHTTP, diese Datei enthält alle benötigten Methoden. Alternativ kann man die Klasse auch zur Laufzeit erstellen, ich bevorzuge diese Methode und gebe deshalb alle Beispiele so an.
Daten Empfangen
Die TIdHTTP-Klasse stellt eine Funktion GET zur Verfügung, damit lässt sich der komplette Quellcode von einer Webseite empfangen:
Delphi-Quellcode:
uses
IdHTTP;
// ...
var
ResponseStr:
string;
begin
with TIdHTTP.Create(
nil)
do
try
// Empfange den Quellcode
ResponseStr := Get('
http://www.delphipraxis.net/');
// in den neueren Indy Releases (10.5.8+) erfolgt das Encoding automatisch,
// vorher musste man sich behelfen (mit einem TMemoryStream oder TStringStream)
// dazu: http://forums2.atozed.com/viewtopic.php?f=7&t=25756
// Mache hier irgendwas mit dem Quellcode
DoSomething(ResponseStr);
finally
Free;
end;
Daten Senden
Zum Senden von Daten wie Login Informationen oder Dateien bietet die Klasse eine Funktion POST. Es gibt nun 3 verschiedene Möglichkeiten, wie man die Daten sendet. Entweder mit einer TStringList, wenn man
keine Dateien senden muss, aber bestimmte Formularfelder ansprechen möchte. Wenn man eine
API ansprechen möchte z.B. Google Blogger dann sendet man, ohne ein spezielles Formularfeld anzusprechen oder man nutzt die Klasse TIdMultiPartFormDataStream aus der
Unit IdMultipartFormData. Oft ist es auch so, dass die Seite vorgibt, wie die Daten gesendet werden müssen. Im
HTML Quelltext sollte man im form-Tag auf das Attribut "enctype" achten.
Hier stand mal das man keine TStringList benutzen sollte und stattdessen einen TStringStream und dann die Werte selber codieren sollte, dass ist bei alten
Indy Versionen notwendig, in den aktuelleren kann man dies aber auch getrost
Indy überlassen - so geht's:
Delphi-Quellcode:
uses
IdGlobalProtocols, IdHTTP;
// ...
var
Params: TStringList;
Enc: TEncoding;
ResponseStr: string;
begin
with TIdHTTP.Create(nil) do
try
// der ContentType beschreibt in welchem Format die Daten an
// den Server gesendet werden
Request.ContentType := 'application/x-www-form-urlencoded';
Params := TStringList.Create;
try
with Params do
begin
Add('vb_login_username=' + AccountName);
Add('vb_login_password=' + AccountPassword);
Add('securitytoken=guest');
Add('do=login');
Add('vb_login_md5password=');
Add('vb_login_md5password_utf=');
Add('cookieuser=1');
Add('s=');
end;
// Request.CharSet vorher setzten: z.B: ISO-8859-1
Enc := CharsetToEncoding(Request.CharSet);
try
// Daten senden
ResponseStr := Post('http://www.delphipraxis.net/login.php?do=login', Params, Enc);
finally
Enc.Free;
end;
finally
Params.Free;
end;
finally
Free;
end;
Delphi-Quellcode:
uses
IdGlobalProtocols, IdHTTP;
// ...
var
Data: TStringStream;
ResponseStr:
string;
begin
with TIdHTTP.Create(
nil)
do
try
Request.ContentType := '
application/atom+xml';
with Request.CustomHeaders
do
begin
Add('
GData-Version: 2');
Add('
Authorization: GoogleLogin auth=' +
{Hier käme ein Autorisierungswert rein});
end;
// Hier kann auch TStringStream.Create('', CharsetToEncoding(Request.CharSet)) genutzt werden
Data := TStringStream.Create('
', CP_UTF8);
try
Data.WriteString( newPageXMLDoc() );
// interne Funktion zum erstellen der API Requests
Request.CharSet := '
UTF-8';
ResponseStr := Post('
http://www.blogger.com/feeds/' + BloggerSettings.id + '
/posts/default', Data);
finally
Data.Free;
end;
// Wenn ResponseCode = 201 dann war das Eintragen erfolgreich.
Delphi-Quellcode:
uses
IdHTTP, IdMultipartFormData, IdGlobalProtocols;
// ...
var
Params: TIdMultiPartFormDataStream;
ResponseStr:
string;
_filename:
string;
begin
with TIdHTTP.Create(
nil)
do
try
// der ContentType beschreibt in welchem Format die Daten an
// den Server gesendet werden
Request.ContentType := '
multipart/form-data';
Params := TIdMultiPartFormDataStream.Create;
try
with Params
do
begin
// Für kleinere Text oder Zahlenwerte, wo man sicher gehen kann, dass es
// nicht zu Kodierungsproblemen kommen kann bietet sich diese Methode an
AddFormField('
einfachertext', '
text');
// In der neueren Version ist diese Funktion zwar deprecated, aber um wirklich
// sicher zu gehen, dass die Daten richtig Kodiert sind verwende ich diese Variante.
// Der erste Parameter gibt den Elementnamen und der letze den Elementwert an.
// für alte Indy's: AddObject('text_in_utf8', '', '', TStringStream.Create('text', TEncoding.UTF8));
// Ab den aktuellen Funktioniert dies analog:
AddFormField('
text_in_utf8', '
text', '
UTF-8').ContentTransfer := '
binary';
// zu ContentTransfer: http://stackoverflow.com/questions/7189794/indy-adds-at-every-72nd-char-with-multi-part-form-data-post
// Das äquivalent zu oben nur für Webseiten die im ISO-8859-1 kodiert sind
// für alte Indy's: AddObject('text_in_ascii', '', '', TStringStream.Create('text', TEncoding.ASCII));
AddFormField('
text_in_ascii', '
text', '
ISO-8859-1').ContentTransfer := '
binary';
// Wie versprochen kann man hiermit auch Dateien senden, damit der MIME-
// Type passt sollte man die GetMIMETypeFromFile() Funktion aus der Unit
// IdGlobalProtocols verwenden.
AddFile('
file', _filename, GetMIMETypeFromFile(_filename));
end;
// Daten senden
ResponseStr := Post('
http://deineseite.com/', Params);
finally
Params.Free;
end;
finally
Free;
end;
Request Einstellungen
Der HTTP Client sendet beim Empfangen oder Senden von Daten nebenher noch weitere Informationen. Eine haben wir eben schon kennengelernt, den ContentType. Die Definition von diesen Werten ist eigentlich optional, jedoch gibt es viele Server die Probleme mit den Standardangaben haben.
Delphi-Quellcode:
with TIdHTTP.Create(
nil)
do
try
// Die Accept Angabe definiert, welche Formen von Daten der Client akzeptiert
Request.Accept := '
text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1';
// Der AcceptCharSet Wert definiert, welche Zeichen-Formate der Client akzeptiert
Request.AcceptCharSet := '
iso-8859-1, utf-8, utf-16, *;q=0.1';
// Die AcceptEncoding Angabe definiert, welche Kompressionsformate der Client akzeptiert
Request.AcceptEncoding := '
deflate, identity, *;q=0';
Request.Connection := '
Keep-Alive';
// Der Referer definiert, auf welcher Webseite wir zuvor waren. Gerade dieser Wert
// wird gerne von Webseiten abgefragt um ungewünschte Bots zu blocken.
Request.Referer := '
http://deineseite.com/';
// Die Client Erkennung, um sich zu tarnen benutze ich gerne den Opera User-Agent
Request.UserAgent := '
Opera/9.80 (Windows NT 6.1; U; de) Presto/2.5.22 Version/10.51';
//...
Response Informationen
Wenn man den Quelltext von einer Webseite empfängt werden noch weitere Protokoll Informationen gesendet.
Delphi-Quellcode:
//...
ResponseStr := Get('http://www.delphipraxis.net/');
// eine Mögliche Form der Weiterleitung an eine andere Adresse oder Unterseite
// ist der Location-Header.
ShowMessage(Response.Location);
// Natürlich hat man auch auf die ganzen Statuscodes einen Zugriff (á la 404 Not Found) mit
// Response.ResponseCode und Response.ResponseText
// Alle Header Informationen kann man so Ausgeben:
ShowMessage(Response.RawHeaders.Text);
//...
Weitere Einstellungen
Neben den ganzen Header Informationen kann man natürlich noch Timeouts und beispielsweise das Verhalten bei Weiterleitungen definierten.
Delphi-Quellcode:
with TIdHTTP.Create(nil) do
try
// Behandle Weiterleitungen (Standard = False)
HandleRedirects := True;
// Leite Maximal X mal weiter (Standard = 15)
// Meistens reicht eine Weiterleitung, mehr ist oft nur Trafficwaste
RedirectMaximum := 1;
// Zeit in Millisekunden, wie lange gewartet wird, bis...
// ...der Client zum Server verbunden ist
ConnectTimeout := 5000; // 5 Sekunden
// ...der Client alle geforderten Daten vom Server gelesen hat
ReadTimeout := 15000; // 15 Sekunden
//...
2. Cookies
Hiermit handelt es sich nicht um das Problem, dass es keine Kekse gibt, sondern um kleine Dateien, die eine Webseite auf dem Client erstellt um Daten wie z.b. eine Sitzung's-ID zu speichern. Auch die HTTP Komponente unterstützt dies. Dazu reicht diese Einstellung:
Delphi-Quellcode:
uses
IdHTTP, IdURI;
//...
with TIdHTTP.Create(nil) do
try
// der Cookie Manager wird nun automatisch angelegt
AllowCookies := True;
// Man kann natürlich Cookies auch modifizieren:
//... irgendwelche Daten werden empfangen, dabei setzt der Server Cookies
with CookieManager.CookieCollection do
for I := 0 to Count - 1 do // alle Cookies durchsuchen
if ('CookieName' = Cookies[I].CookieName) then
begin
MacheWasMitDemCookieWert(Cookies[I].Value);
break;
end;
// Cookies werden so erstellt:
// TIdURI Klasse aus: IdURI
CookieManager.AddServerCookie('werbung_aus=1', TIdURI.Create('http://www.delphipraxis.net/'));
//...
Hinweis: Die Cookie Implementation ist erst seit 10.5.8 wirklich alterstauglich.
3. Gzip
Je umfangreicher die Webseite, desto mehr Quelltext muss geladen werden und umso länger dauert dies auch. Mit Gzip kann, wenn der Server das unterstützt der Datenaustausch komprimiert werden. Dazu muss zusätzlich die
Unit IdCompressorZLib eingebunden werden.
Delphi-Quellcode:
uses
IdHTTP, IdCompressorZLib;
// ...
with TIdHTTP.Create(
nil)
do
try
Compressor := TIdCompressorZLib.Create(
nil);
// Beim Freigeben der HTTP Komponente wird der Compressor mit freigegeben.
// Nun dürfen wir im AcceptEncoding definieren, dass gzip Unterstützt wird
Request.AcceptEncoding := '
deflate, gzip, identity, *;q=0';
// Manche Server machen Probleme mit der Kompression, sodass es zu Fehlern kommen kann,
// diese kann man leicht abfangen, die Dekompression klappte bei mir jedoch immer richtig.
try
// Bei Indy Versionen älter 10.5.8 muss man hierbei immer mit Streams arbeiten, weil sonst werden die Daten nicht dekomprimiert:
// Post('http://deineseite.com/', Params, ReplyData); // ReplyData vom Typ TMemoryStream
// ReplyData.Position := 0; nicht vergessen :)
// ResponseStr := ReadStringAsCharset(ReplyData, Response.Charset);
QuellcodeStringVariable := Post('
http://deineseite.com/', Params);
except
on E: EDecompressionError
do
;
// Mache nichts (ich weiß das sieht nicht professionell aus)
// Weitere Fehlerbehandlungen
end;
//...
4. SSL
Das HTTPS Protokoll verschlüsselt den kompletten Datenverkehr. Vorab: Hierfür werden die passenden Versionen der libeay32.dll und ssleay32.dll Bibliothek benötigt. Die aktuelle Version findet man immer in diesem
Thema. Die DLLs müssen ins root-Verzeichnis eurer Applikation. Zusätzlich müssen wir die
Unit IdSSLOpenSSL einbinden.
Delphi-Quellcode:
uses
IdHTTP, IdSSLOpenSSL;
// ...
with TIdHTTP.Create(nil) do
try
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
//...
5. HTTP Proxy / SOCKS 4, 5
Was ich euch hier zeigen möchte ist eine Kombi-Lösung für die verschiedenen Proxy Varianten, d.h. man sollte sich eine eigene Proxy Klasse schreiben mit einem Proxy-Typ, der dann generell alles ermöglicht. Damit dies auch in Kombination mit SSL funktioniert habe ich auch dies eingebaut. Natürlich könnte man, wenn man wirklich nur SOCKS 5 benötigt das dementsprechend vereinfachen, aber wie gesagt, hier ein flexibles Beispiel:
Delphi-Quellcode:
uses
IdHTTP, IdSSLOpenSSL, IdSocks;
// ...
var
FIdSSLIOHandlerSocketOpenSSL: TIdSSLIOHandlerSocketOpenSSL;
FIdSocksInfo: TIdSocksInfo;
begin
with TIdHTTP.Create(nil) do
try
FIdSSLIOHandlerSocketOpenSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
with FIdSSLIOHandlerSocketOpenSSL.SSLOptions do
begin
Method := sslvTLSv1_2;
SSLVersions := [sslvTLSv1_2];
end;
FIdSocksInfo := TIdSocksInfo.Create(nil);
if { SOCKS wird benutzt ??? } then
with FIdSocksInfo do
begin
Host := 'Server';
Port := 8080; // Beispiel Port
if { SOCKS Version = ??? } then
Version := svSocks4
else
Version := svSocks5;
case { RequireAuthentication ??? } of
True:
Authentication := saUsernamePassword;
False:
Authentication := saNoAuthentication;
end;
Username := 'AccountName';
Password := 'AccountPassword';
// Nun SOCKS aktivieren
Enabled := True;
end
else // normale HTTP-Proxy wird benutzt
with ProxyParams do
begin
ProxyServer := 'Server';
ProxyPort := 80; // Beispiel Port
BasicAuthentication := True; // RequireAuthentication ???
ProxyUsername := 'AccountName';
ProxyPassword := 'AccountPassword';
end;
// Erst dem SSL-Handler die Mögliche Proxy/SOCKS Konfiguration zuweisen
// andersrum greift es nicht!
FIdSSLIOHandlerSocketOpenSSL.TransparentProxy := FIdSocksInfo;
// Jetzt der HTTP Komponente zuweisen
IOHandler := FIdSSLIOHandlerSocketOpenSSL;
//...
6. Konklusion
Wie man sieht unterstützt die HTTP Komponente alle nötigen Funktionen eines Browsers. Ich höre/lese immer wieder, dass eine Webseite nicht auslesen werden kann, weil die Komponente kein JavaScript unterstützt. Diese Aussage kann ich zwar nicht widerlegen, jedoch ist mir noch keine Webseite unter den Nagel gekommen, wo die Komponente an ihre Grenzen gestoßen ist.
Wenn ihr jetzt also selber viele Teile hiervon benötigt, empfehle ich eine eigene Klasse zu schreiben, die von TIdHTTP erbt. Darin setzt ihr alle Voreinstellungen, damit man das nicht bei jeder Verwendung machen muss (okay eigentlich selbstverständlich nur ..., ja genau).
7. Tools
WireShark
Bei der Client/Server Kommunikation können sehr leicht Fehler auftreten, meistens weiß man aber nicht so genau, was überhaupt übertragen wurde, deshalb kann ich jetzt nun schon aus langjähriger Erfahrung dieses umfangreiche und kostenlose Programm empfehlen.
Filter:
Damit man nur den HTTP Datenverkehr angezeigt bekommt.
Code:
http && (http.response.code != 0) || (
tcp.dstport == 80)
FireFox Web Developer
Ich bin wohl der letzte Mensch der freiwillig dauerhaft diesen Browser benutzen würde, jedoch gibt es dafür ein wunderbares
Plugin, welches von einer beliebigen Webseite alle Formularfelder sauber in einer strukturierten Tabelle anzeigen kann. Sprich man muss sich nicht mehr durch den Quelltext-Dschungel kämpfen.
Da dies nun mein erstes Tutorial ist, hoffe ich, dass nicht allzu viel falsch gemacht wurde

.