PHP

PHP & OOP - Webdienste mit PHP nutzen (PHP Tutorial)

Tutorial erstellt von Andreas W. in PHP 5.x, letzte Änderung am 22.06.2010

Vorwort

In dem folgenden Tutorial möchte ich zeigen, wie man mit PHP, Socket-Verbindungen und etwas Verständnis des Hypertext Transfer Protocols (HTTP) auf einfache, sichere und zudem schnelle Weise Daten durch das Netz transportieren kann. So wird zum Beispiel gezeigt, wie man den Quellcode von Websiten anfordert ohne auf quick 'n dirty Code zurückgreifen zu müssen, der diese Operation mit Hilfe der file_get_contents()-Funktion löst, die zu Problemen führen kann, wenn auf dem Server allow_url_fopen deaktiviert ist. Speziell bei der Einbindung bzw. Nutzung von Webdiensten, die viele große Firmen wie Google, Ebay und Yahoo anbieten, kann dieses Wissen sehr hilfreich sein.
Am Ende dieses Tutorials soll eine voll einsatzfähige HTTP-Klasse stehen. Mit Hilfe dieser Klasse wird es ein Kinderspiel sein, zum Beispiel den Onlinestatus eines Users von icq.com abzufragen.
Bei all seinem praktischen Reizen soll aber auch der Themenkomplex rund um die im Hintergrund ablaufenden Prozesse und die Geschichte des Internets nicht zu kurz kommen. Aber nun genug der Rederei, lasset uns zur Tat schreiten...

Hyper Text Transfer Protocol (HTTP)

Das Hypertext Transfer Protocol (HTTP, dt. Hypertext-Übertragungsprotokoll) ist ein Protokoll, dass die Übertragung von Daten über ein Netzwerk ermöglicht. Dieses Protokoll wird hauptsächlich in Browsern eingesetzt, um Webseiten und andere Daten aus dem World Wide Web (WWW) zu laden.

Durch die Aufstockung der Anfragemethoden, Header-Informationen und Statuscodes ist das HTTP nicht mehr auf Hypertext beschränkt. Es wird zunehmend auch zum Austausch beliebiger Daten verwendet.

Zum momentanen Zeitpunkt werden zwei Versionen des Protokolls verwendet (HTTP/1.0 und HTTP/1.1). Während man um eine Website, die n Bilder enthält, mit HTTP/1.0 zu laden, n+1 TCP-Verbindungen benötigte, kann das selbe HTML-Dokument via HTTP/1.1 nur mit einer TCP-Verbindung geladen werden. Dies liegt daran, dass HTTP/1.0 vor jeder Anfrage eine neue Verbindung aufbaut, die nach der Anfrage wieder geschlossen wird.
Im Allgemeinen ist HTTP jedoch ein zustandsloses Protokoll. Das heißt, dass Verbindungen nach einer erfolgreichen Datenübertragung nicht aufrechterhalten zu werden brauchen.

Das Protokoll wurde 1989 von Tim Berners-Lee am CERN zusammen mit URL (Uniform Resource Locator) und HTML (Hypertext Markup Language) entwickelt, wodurch praktisch das World Wide Web geboren wurde. 1994 gründete Berners-Lee das World Wide Web Consortium (W3C), dass vielen unter euch bekannt sein dürfte. Besonders wichtig für die Entwicklung des WWW war es, dass Berners-Lee seine Ideen und praktischen Umsetzungen nicht patentierte, sondern frei publizierte. Dieser Einstellung ist es unter anderem zu verdanken, dass das World Wide Web Consortium ausschließlich patentfreie Standards verabschiedet.

Kommunikation

Die Kommunikation zwischen Server und Client ist in zwei Abschnitte unterteilt. Als erstes sendet der Client eine Request (dt. Anfrage) an den Server. Dieser verarbeitet die Anfrage und schickt eine Response (dt. Antwort) an den Client zurück.

Sowohl Anfrage als auch Antwort sind nochmals in zwei Teile unterteilbar. Ich werde im Folgenden Anfrage und Antwort als Message (dt. Nachricht) zusammenfassen. Jede Nachricht lässt sich in Header (dt. Kopf) und Body (dt. Körper) aufteilen. Der Header enthält wichtige Daten, wie die verwendete HTTP-Version, den Zeitpunkt des Requests, Statuscode, Art der Verschlüsselung (falls verschlüsselt) und noch einige andere Informationen. Ähnliche Informationen sollten euch aus dem Kopf einer Email bekannt sein (siehe Simple Mail Transfer Protocol - SMTP), der zum Beispiel den Absender, den Empfänger und den Betreff enthält.
Der Body enthält die eigentlichen Daten, zum Beispiel den HTML-Code einer Website.

Funktionsweise

Wie bereits erwähnt ist HTTP ein Protokoll, das es ermöglicht, Daten durch das Netz zu transportieren. Anders gesagt ist HTTP ein Kommunikationsschema, das Befehle zum Senden und Empfangen von Requests und Responses zur Verfügung stellt. Gibt man zum Beispiel die Adresse http://www.example.de in der Adresszeile eines Browsers ein, so wird ein Request an den Computer mit dem Hostnamen www.example.de geschickt, der die (falls nötig) geparste Index-Seite zurücksendet. Da man im Netz nicht wirklich viel mit Namen anfangen kann, wird die URL www.example.net zuerst über das DNS-Protokoll in eine IP-Adresse umgewandelt. Die eigentliche Übertragung findet dann über das TCP-Protokoll auf dem Standard-Port 80 des HTTP-Servers statt, der einen HTTP-GET-Request sendet. Ein solcher GET-Request könnte also wie folgt aussehen:

Code:
GET /index.html HTTP/1.1
Host: www.example.de


Falls eine URL Sonderzeichen beinhaltet, die in einem Request nicht erlaubt sind, werden diese kodiert. Möchte man zusätzliche Informationen, wie zum Beispiel Spracheinstellungen oder Browserinformationen an den Zielserver übermitteln, so können diese im Header des Requests deklariert werden. Sobald der Request-Header mit einer Leerzeile abgeschlossen wird, sendet der Zielserver seinerseits einen Response, der sich ebenfalls aus Header-Daten, wie Sprache, Dateilänge und Kodierung zusammensetzt. Der Header wird wiederum mit einer Leerzeile abgeschlossen und es folgen die eigentlichen Daten (engl. Content), in unserem Fall die Index-Datei.
Üblicherweise werden als Datensätze Dokumente, die in in darstellenden Sprachen, wie zum Beispiel (X)HTML, verfasst sind, übermittelt. Natürlich ist heute eine Website rein auf (X)HTML-Basis kaum noch denkbar und so werden selbstverständlich auch alle damit zusammenhängenden Daten, wie Bilder, Stylesheets (CSS) und JavaScript-Dokumente übertragen. Es können praktisch alle Datentypen übertragen werden, so auch PHP-, Pearl- oder Phyton-Dokumente, jedoch werden dabei nicht die eigentlichen Dateien sondern nur die von diesen Dateien erzeugte Ausgabe übertragen. Doch gerade dies ist für die Nutzung von Webdiensten, wie wir es vorhaben, besonders wichtig. Wie sieht nun aber so ein Response eigentlich aus?

Code:
HTTP/1.1 302 Found
Date: Tue, 09 Dec 2008 00:15:21 GMT
Server: Apache
Content-Length: (Größe der Index-Seite in Byte)
Content-Type: text/html; charset=iso-8859-1
Connection: close

(Inhalt der Index-Seite)


Selbstverständlich kann ein solcher Kommunikationsversuch auch einmal fehlschlagen. In diesem Fall sendet der Server eine Fehlermeldung.

Die wichtigsten Request-Befehle

HTTP bietet uns einige Request-Methoden, die es uns ermöglichen, die gewünschten Anfragen an einen Server zu stellen. Da jedoch für unser Vorhaben viele dieser Methoden uninteressant sind und einige auch kategorisch von allen Webhosts gesperrt werden, werden hier nur die drei wichtigsten Methoden mit einer kurzen Erläuterung aufgeführt.
  • HEAD ist die kleinste und auch am einfachsten zu implementierende Methode. Sie erfragt vom Server lediglich den Heaader einer Datei, wie er bei einem GET- oder auch POST-Request mitgesendet werden würde. Der eigentlich Inhalt des Dokuments wird folglich nicht übertragen. Anhand des Statuscodes kann man so zum Beispiel herausfinden, ob einen die angegebene URL auf eine andere umleitet.

  • GET ist die gebräuchlichste Methode. Jeder der in PHP schon mit URL-Parametern gearbeitet hat kennt sie. Mit ihrer Hilfe ist es möglich, Dateien von einem Server anzufordern.

  • POST ähnelt der GET-Methode, jedoch werden in einem POST-Request zusätzliche Daten übermittelt. Diese Daten stammen in der Regel aus irgendwelchen Formularen und sollen nun diskret übermittelt werden.
    Diese Daten könnten, wie bereits eben angesprochen, auch über die URI (Uniform Resource Identifier) als URL-Parameter übermittelt werden, jedoch sind sie dort für jederman sichtbar und können leicht verändert werden. Möchte man also sensible Daten (Passwörter oder andere personenbezogene Daten) übermitteln, sollte man dafür die POST-Methode verwenden.

Senden von HTTP-Requests

Wie ich bereits erwähnte, ist das Übermitteln von Zusatzinformationen wie einem Nutzernamen oder einer Artikelnummer auf zwei Arten möglich, die wir uns nun genauer anschauen werden. Man sollte zuvor aber noch wissen, dass diese Daten falls nötig URL-kodiert werden müssen. Reservierte Zeichen werden so durch den entsprechenden ASCII-Hexacode und Leerzeichen durch ein "+" ersetzt. Der entsprechende Standard kann im RFC3986 nachgelesen werden.

GET-Requests

Möchte man via GET zusätzliche Daten übermitteln, so geschieht dies über den Query String (dt. Anfrage-Teil), der mit einem "?" an die URL einfach angehängt wird. Sollen mehrere Wertpaare übermittelt werden, so werden alle weiteren mit einem "&" angehängt. Die Wertpaare ansich setzen sich aus einem Key (dt. Schlüssel) und einem Value (dt. Wert) zusammen.

Möchte man zum Beispiel die ICQ Nummer eines Nutzers an die Status-Abfrage von ICQ senden, so wird diese einfach mit dem Key "icq" an die URL angehängt.

Code:
GET /online.gif?icq=237076093 HTTP/1.1
Host: status.icq.com


Dieser Request sagt nun dem Browser, dass wir gerne den Onlinestatus von mir abfragen möchten. Die Anfrage wird verarbeitet ohne eine Datei zu übermitteln. Stattdessen werden wir je nach meinem Onlinestatus via Location-Header auf das Bild /0/online1.gif oder /0/online2.gif weitergeleitet. Da uns diese Daten im Header des Responses übermittelt werden, können wir diese einfach abfragen und entsprechend den Status des Users festlegen.

POST-Requests

Da die Daten bei einem POST-Request nicht über die URL sondern direkt im Request übermittelt werden, können auch deutlich größere Datenmengen als bei einem GET-Request übertragen werden. So ist das Übermitteln eines Bildes überhaupt kein Problem. Die Tatsache, dass diese zusätzlich im Request selbst und nicht via URL übergeben werden, machen dieses Verfahren deutlich sicherer, was auch der Grund dafür ist, dass Formulardaten eigentlich immer über einen POST-Request versendet werden. Wie sieht nun aber unser obiges Beispiel in Verbindung mit einem POST-Request aus?

Code:
POST /online.gif HTTP/1.1
Host: status.icq.com
Content-Type: text/html; charset=iso-8859-1

icq=237076093


Übersicht über die Statuscodes

Da es grundlegend ist Fehlermeldungen zu lesen und zumindest ansatzweise ihre Bedeutung zu verstehen, damit man einen Algorithmus entsprechend verbessern bzw. anpassen kann, werde ich im folgenden noch etwas genauer auf die Statuscodes, die im Header jedes HTTP-Responses mitgesendet werden, eingehen. Im Allgemeinen lassen sich alle Statuscodes in folgende Kategorien unterteilen:
  • 1xx - Ist im Prinzip nur ein Statusbericht, der verhindert, dass der Client nach einer gewissen Zeit aufgrund eines Timeouts (Zeitspanne ohne Aktionen) automatisch einen Fehler annimmt. Die Anfrage ist also noch nicht fertig bearbeitet worden.

  • 2xx - Die Anfrage konnte erfolgreich verarbeitet werden.

  • 3xx - Die Anfrage kann nicht vollends bearbeitet werden, da weitere Befehle des Clients nötig sind, weil auf eine andere URL weitergeleitet werden soll. Dies ist zum Beispiel der Fall bei unserer Anfrage an den ICQ-Server. Dieser möchte uns vom angegebenen Link auf einen anderen weiterleiten. Dies wird uns neben dem entsprechenden Statuscode auch über den Location-Header mitgeteilt, in dem die neue URL steht.

  • 4xx - Es ist ein Fehler bei der Verarbeitung der Anfrage aufgetreten. Dieser Fehler resultiert aus falschen Daten, die vom Client übermittelt wurden. Der wohl bekannteste dieser Fehler ist wohl der Fehler 404. Dieser Fehler weist darauf hin, dass das angeforderte Dokument nicht unter der angegebenen Adresse auf dem Server zu finden ist.

  • 5xx - Es ist ein Fehler seitens des Servers aufgetreten, da dieser zum Beispiel nicht über die benötigte Software verfügt.


Nachfolgend möchte ich noch einmal kurz die wichtigsten Statuscodes auflisten. Diese Meldungen könnten unter gewissen Umständen häufiger auftreten.
  • 200 OK - Die Anfrage konnte erfolgreich verarbeitet werden.

  • 204 No content - Die Anfrage wurde erfolgreich bearbeitet, jedoch wird nur ein Header, aber kein Body gesendet.

  • 301 Moved permanently - Die Datei ist unter der angegebenen Adresse nicht mehr erreichbar. Die neue Adresse wird im Location-Header übertragen.

  • 302 Found Dieser Statuscode ist eigentlich veraltet, ist aber dennoch häufig zu finden. (vgl 307)

  • 307 Moved temporarily - Die angeforderte Adresse ist momentan nicht erreichbar. Die Datei wird vorübergehend unter der im Location-Header mitgesendeten Adresse erreichbar sein.

  • 400 Bad request - Der Client hat eine syntaktisch falsche Anfrage gestellt.

  • 403 Forbidden - Der Zugriff wird verweigert.

  • 404 Not found - Das Dokument kann unter der angegebenen Adresse nicht gefunden werden.

  • 500 Internal Server Error - Es ist ein Problem beim Server aufgetreten.

  • 505 HTTP version not supported - Der Server unterstützt die vom Client angegebene HTTP-Version nicht.


Authentifizierung

Um endlich mit den Hintergünden zum Hypertext Transfer Protocol abzuschließen, möchte ich nun noch kurz auf die Authentifizierung gegenüber einem Server via HTTP eingehen.
Man stelle sich vor, man möchte den Status seiner Ebay-Transaktionen via HTTP abfragen. An dieser Stelle kann man sich ziemlich sicher sein, dass der Server eine Authentifizierung des Nutzers gegenüber dem Server verlangen wird. Das ist auch gut und richtig so, denn wer will schon, dass jeder, unter Angabe der Kundennummer oder vielleicht sogar nur dem Namen, private oder personenbezogene Daten einfach abfragen kann. Es gibt zwei Arten der Authentifizierung Basic Authentication und Digest Access Authentication. Ich werde jedoch nur kurz auf die erste eingehen. Die Basic Authentication ist nämlich die im Web am weitesten verbreitete der beiden HTTP-Authentifizierungen und zudem die einfachere. Möchte man sich mit ihr an einem Server authentifizieren, so muss man nur Benutzername und Passwort, durch einen Doppelpunkt getrennt und Base64 kodiert, an den Server senden.

Request for Comments

Die Requests for Comments (RFC; dt. Forderung nach Kommentaren) sind eine enorm große Sammlung von technischen und organisatorischen Dokumenten zum Internet. Sie beschreiben Quasi-Standards oder "Best Current Practices" (dt. beste momentane Umsezung), wie zum Beispiel, das File Transfere Protocol (FTP), das Simple Mail Transfere Protocol (SMTP) und den Internet Relay Chat (IRC), aber zum Beispiel auch die Konvention einer IP-Adresse oder einer Email-Adresse. Die RFCs behalten auch dann ihren Namen, wenn sie sich durch allgemeine Akzeptanz und Gebrauch zum Standard entwickelt haben. Da dieses Tutorial vielen RFC-Dokumenten zugrunde liegt, sind hier die wichtigsten nochmals aufgelistet.

Anforderungen an die Klasse

Da wir nun wissen, wie das Protokoll, das wir in unsere PHP Klasse implementieren möchten, aufgebaut ist und funktioniert, können wir anfangen uns Gedanken über die Struktur der Klasse zu machen. Dazu sollten wir uns als erstes überlegen, was fundamentale Abläufe sind, die wir innerhalb der Klasse öfter verwenden oder die unbedingt von anderen Programmabschnitten getrennt sein müssen. Variablen, auf die sehr oft zurückgegriffen werden muss, sollten dabei nicht außer Acht gelassen werden.

Um mit einem Server kommunizieren zu können, brauchen wir dessen Host-Adresse und den zu nutzenden Port (Standardport 80). Des weiteren erwarten viele Server, dass man bei einem HTTP-Request den Referrer (dt. Anfragender) angibt. Außerdem sollten wir eine Variable deklarieren, die speichert, ob eine gesicherte Verbindung via SSH (Secure Socket Layer) hergestellt werden soll oder nicht.
Abschließend sollten wir, vor allem zur Fehlersuche, das Senden und Empfangen von Befehlen unsererseits mitloggen können. Folglich brauchen wir eine Variable, die festlegt, ob mitgeloggt wird oder nicht und eine, die das Logging enthält. Aufgrund dieser Überlegungen erhalten wir folgende Variabeln für unsere Klasse:

Code:
   private $host;
   private $port;
   private $referrer;
   private $ssl;
   private $logStats;
   private $log;


Nachdem wir nun also die Eigenschaften für unsere Klasse festgelegt haben, wollen wir uns den Methoden der Klasse zuwenden. Wir benötigen Methoden, mit deren Hilfe die Klassen-Eigenschaften deklariert, eine Verbindung zum Zielserver hergestellt und wieder geschlossen werden kann. Eine Methode zur Authentifizierung darf natürlich nicht fehlen. Nun muss noch die eigentliche Kommunikation mit dem Server geregelt werden. Dazu müssen Methoden implementiert werden, die Befehle senden, Antworten loggen, Antworten auslesen, die Rückgabe vom Server verarbeiten und die Antwort vom Server dekodieren können. Da dies aber eine Klasse werden soll, die das Handling von HTTP-Requests via PHP erleichtert, müssen noch Protokoll-spezifische Methoden eingebunden werden. Wir werden uns dabei an den uns bekannten Kommunikationsmöglichkeiten orientieren. Aufgrund unserer Überlegungen gelangen wir zu folgender Methoden-Zusammenstellung:

Code:
   public function connect()
   private function login()
   private function auth()
   private function logout()
   private function cmd()
   private function readSock()
   private function divideReply()
   private function getName()
   private function decodeChunked()
   public function head()
   public function get()
   public function post()
   public function log()
   public function __toString()


Somit haben wir nun eine Struktur für unsere Klasse und können mit dem Schreiben der Methoden beginnen.

Implementierung der fundamentalen Abläufe in PHP

Bevor irgendetwas an den Server übermittelt werden kann, muss natürlich eine Verbindung mit dem Server aufgebaut werden. Wie bereits angedacht übernimmt diese Aufgabe die Methode login(). Bei dem Erstellen dieser Methode müssen wir allerdings darauf achten, dass wir zwischen einer "normalen" und einer gesicherten Verbindung via SSL unterscheiden. Zudem ist es wichtig die maximale Verbindungsdauer hochzusetzen. So ergibt sich eine Methode, die uns ein Verbindungs-Objekt zurückliefert.

Das Schließen der durch die login()-Methode erstellten Verbindung geschieht einfach mittels der Funktion fclose().

Nun könnten wir rein theoretisch bereits Anfragen an den Server senden, jedoch erlaubt es nicht jeder Server Anfragen zu senden, ohne sich vorher vorgestellt zu haben. Daher implementieren wir die Methode auth(), mit deren Hilfe wir uns gegenüber dem Zielserver authentifizieren können.

Nachdem wir uns nun auch gegenüber dem Zielserver vorstellen können, können wir uns den Methoden zum Senden von Anfragen zuwenden. Diese haben alle den selben Aufbau. Ihnen wird ein optionaler Parameter $uri übergeben, der die Zieldatei der Anfrage festlegt. Des weiteren können ebenfalls optional Parameter übergeben werden, die von der Zieldatei verarbeitet werden können. Diese werden im Falle eines GET-Requests zum Beispiel an die URL angehängt. Da dies allerdings sehr unsicher ist, werden diese bei einem POST-Request innerhalb des Bodys übertragen und sind so nicht ohne weiteres sichtbar.
Als dritter ebenfalls optionaler Parameter können Cookies übergeben werden. In der Regel sollte dies nicht nötig sein, jedoch ist es sinnvoll eine entsprechende Implementierung vorzunehmen, da Cookies einfach über einen zusätzlichen Parameter im Kopf des Requests übermittelt werden können. Abschließend können ebenfalls optional ein Nutzername und ein Passwort zur Authentifizierung gegenüber dem Zielserver angegeben werden. Nun haben wir alles zusammen um die Methoden zum Senden eines GET- oder HEAD-Requests zu implementieren. In beiden wird jeweils zuerst eine Verbindung mit dem Zielserver mittels der Methode login() aufgebaut. Falls nötig authentifiziert sich dann der Client gegenüber dem Zielserver. Darauf folgt die Vorbereitung der Parameter und Cookies, falls diese übergeben wurden. Ist dies geschehen, wird auch bereits der Header des jeweiligen Request zusammengefügt. Der Body bleibt leer. Der Request wird gesendet, die Antwort ausgelesen und verarbeitet. Die Verbindung mit dem Zielserver wird getrennt und die verarbeiteten Daten in einem Array zurückgegeben. Die Implementierung des POST-Requests sieht im Grunde genauso aus, jedoch können dort zwei weitere optionale Parameter $fileParams und $mimes übergeben werden, mit deren Hilfe Dateien übermittelt werden können. Diese Daten werden ebenfalls vorbereitet und dann teils im Header, teils im Body übermittelt.

Besonders interessant ist noch die Methode decodeChunked(). Da, wenn wir eine Anfrage an eine PHP- oder ähnliche Datei senden, der Quelltext erst zur Laufzeit generiert wird, kann der Zielserver die Größe der zurückgesendeten Daten nicht festlegen. Somit ermittelt er die Größe jeder Zeile, gibt diese in HexaCode an und gibt danach die entsprechende Zeile aus. Schauen wir uns den unverarbeiteten Response an, sieht das grauenhaft aus. Zum Glück lässt sich das ganze mit Hilfe eines einfachen Algorithmus lösen, mit dessen Hilfe wir auch die Größe des übermittelten Pakets feststellen können.

Code:
procedure decodeChunked( String chunked )
   String body = null;
   Integer length = null;
   Integer size = null;
   Array data = null;

   while(true)
      // get next occurrence of CRLF
      pos = find_next("\r\n", chunked);

      if( !(pos === false) AND (size === null) )
         // get the size of following chunk from hex
         size = hexdec(substr(chunked, 0, pos));

         // get the following chunk
         body = body + substr(chunked, pos+2, size);

         // update the content not encoded
         chunk = substr(chunked, pos+2+size);

         // update content-length
         length = length + size;

         // reset chunk-size
         size = null;
      else
         // leave loop
         break;
      endif;
   endwhile;

   add(chunked, data)
   add(length, data)

   return data;
end procedure


Zusammenführen der HTTP-Klasse

Code:

<?php

/**
* @class HttpConnect
*
* The HttpConnect class allows easy connecting
* to a server. After connecting to your server
* you are able to send a post, get or a head command.
* So it is possible to send or to get different information
* to or from the server. Finally it is important to mention that
* this class supports secure sockets layer connections (ssl).
*
* @author Andreas Wilhelm
* @version
* Version 0.1, 25.01.2008<br />
* Version 0.2, 04.04.2010 (Now using arrays to create parameter-lists)<br />
* Version 0.3, 06.04.2010 (Added proxy support)<br />
*/
class HttpConnect {

    /**
     * @private
     * The host's name.
     */
    private $host;

    /**
     * @private
     * The port of the ftp-server.
     */
    private $port;
  
    /**
     * @private
     * Indicates if a ssl connection should be used.
     */
    private $ssl;
  
    /**
     * @private
     * The host name of the proxy server.
     */
    private $proxy;
  
    /**
     * @private
     * The port of the proxy server.
     */
    private $proxyport;
  
    /**
     * @private
     * Tip by Denis Wronka, who said, that many servers need a referrer.
     */
    private $referrer;
    
    /**
     * @private
     * The logging status.
     */
    private $logStats;

    /**
     * @private
     * The logging data.
     */
    private $log;

    /**
     * @public
     *
     * Sets the server-variables
     *
     * @param String $host The name of the host.
     * @param Integer $port The used port.
     * @param Boolean $ssl Declares if a secure connection should be used.
     * @param Boolean $log Declares if the connection data should be logged.
     * @param String $referrer The name of the HTTP-Referrer.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     * Version 0.3, 06.04.2010 (Added proxy support)<br />
     */
   public function connect($host='localhost', $port=80, $ssl=false, $proxy=false, $proxyport=443, $log=false, $referrer="PHP/HTTP-Class") {
        // Declare if the connection with the ftp-server should be logged or not.
        $this->logStats = $log;
        
        // Store the host name and password, ...
        $this->host = $host;
        $this->port = $port;
        
        // ... the proxy and its port, ...
        $this->proxy = $proxy;
        $this->proxyport = $proxyport;

        // ... the referrer's name and the connection state.
        $this->ssl = $ssl;
        $this->referrer = $referrer;
   }

    /**
     * @private
     *
     * Connects to the server
     *
     * @param String $host The name of the host.
     * @param Integer $port The used port.
     * @return The connection handle.
     * @throw Exception
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     * Version 0.3, 06.04.2010 (Added proxy support)<br />
     */
    private function login() {
        // Check if a secure connection ...
        if ($this->ssl == true) {
            // ... and a proxy server should be used ...
            if ($this->proxy!=false) {
                $handle = @fsockopen('ssl://'.$this->proxy, $this->proxyport);
            }

            // ... or not ...
            else {
                $handle = @fsockopen('ssl://'.$this->host, $this->port);
            }

            // Check if the connection was established ...
            if (!$handle) {
                // ... and throw an exception if not.
                throw new Exception("SSL-connection failed.\n");
            }
        }

        else {
            // ... and a proxy server should be used ...
            if ($this->proxy!=false) {
                $handle = @fsockopen($this->proxy, $this->proxyport);
            }

            // ... or not ...
            else {
                $handle = @fsockopen($this->host, $this->port);
            }
        
            // Check if the connection was established ...
            if (!$handle) {
                // ... and throw an exception if not.
                throw new Exception("Connection failed.\n");
            }
      }
        
        // Switch to non-blocking mode ...
        @set_socket_blocking($handle, true);
        
        // ... and set the timeout of the control.
        @stream_set_timeout($handle, 0, 200000);
      
      return $handle;
   }

    /**
     * @private
     *
     * Creates a connection to a secured server.
     *
     * @param String $user The user's name.
     * @param String $password The user's password.
     * @return The authentification code.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function auth($user, $password) {      
        // Check if a valid username was given ...
        if( !empty($user) ) {
            // ... and create the authentification code if it is.
            return 'Authorization: Basic '.base64_encode($user.':'.$password)."\r\n";
        }

        // Otherwise return an empty authentification code.
        return '';
    }
  
    /**
     * @private
     *
     * Closes the connection.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */      
    private function logout($handle) {
        fclose($handle);
    }

    /**
     * @private
     *
     * Sends a command to the server
     *
     * @param String $cmd The command which should be transmit to the server.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function cmd($handle, $cmd) {
        // Send the command to the server ...
        fputs($handle, $cmd);

        // ... and create a log entry.
        $this->log("> $cmd");
    }
  
    /**
     * @private
     *
     * Reads out the response from the server
     *
     * @return The server's response.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function readSock($handle) {
        // Initialize an empty server response, ...
        $response = "";

        // ... read all response data from the connection handle ...
        while(!feof($handle)) {
            $response .= fread($handle, 1025);
        }
      
        // ... and write a log message.
        $this->log($response);
      
        return $response;
    }
        
    /**
     * @private
     *
     * Spilts up the reply into the different information
     *
     * @param String $reply
     * @return The array that contains all response data.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function divideReply( $reply ) {  
        // Check the HTTP-Version and status-code.
        if( !preg_match('/^HTTP\/(1\.[01]) ([1-5][0-9]{2})/', $reply, $match) ) {
            return false;
        }
      
        // Now split header and body, ...
        $hb = strpos($reply, "\r\n")+2;
        $cb = strpos($reply, "\r\n\r\n")+4;
      
        // ... write the data into an array, ...
        $parsed = array(
            'HTTP_VERSION'  => $match[1],
            'STATUS_CODE'   => $match[2],
            'HEADER_FIELDS' => array(),
            'CONTENT'       => (string) substr($reply, $cb)
        );
      
        // ... split up the single header fields,
        $headerFields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', substr($reply, $hb, $cb-$hb-4)));

        // ... and add them into the headers-array.
        foreach( $headerFields as $headerField ) {
            if( preg_match('/([^:]+):(.+)/m', $headerField, $match) ) {
                $parsed['HEADER_FIELDS'][$this->getName($match[1])] = trim($match[2]);
            }
        }

        // Finally check if tranfere-encoding is chunked ...
        if( isset($parsed['HEADER_FIELDS']['Transfer-Encoding']) && $parsed['HEADER_FIELDS']['Transfer-Encoding'] == 'chunked' ) {
            // ... and encode the chunked data if needed.
            return $this->decodeChunked($parsed);
        }
      
        return $parsed;
    }
  
    /**
     * @private
     *
     * Returns the name of a received HTTP-header-field
     *
     * @param String $string The full header.
     * @return The name of the header field.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function getName($string) {
        return preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($string)));
    }  
  
    /**
     * @private
     *
     * Decodes body in chunked encoding
     *
     * @param Array $chunked The chunked data received from the server.
     * @return The data array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function decodeChunked($chunked) {
        // Initialize vars holding the content and the content size.
        $body = '';
        $length = NULL;
        $size = NULL;
      
        // Save the chunked body to a variable ...
        $chunk = $chunked['CONTENT'];

        while(true) {
            // Get the next occurrence of CRLF ...
            $pos = @strpos($chunk, "\r\n", 0);

            // ... and check if it occured or if it is an empty line.
            if(!($pos === false) && $size === NULL) {
                // If there is some content, get the its size, ...
                $size = hexdec(substr($chunk, 0, $pos));
            
                // ... read the chunk, ...
                $body .= substr($chunk, $pos+2, $size);
            
                // ... update the content not encoded ...
                $chunk = substr($chunk, $pos+2+$size);
            
                // ... and the content-length ...
                $length += $size;
            
                // ... and reset the chunk-size.
                $size = NULL;
            }
        
            else {
                // If there is no line to read stop looping.
                break;
            }
        }
      
        // After fetching the content, add it to the data array ...
        $chunked['CONTENT'] = $body;
      
        // ... refresh the content-length ...
        $chunked['HEADER_FIELDS']['Content-Length'] = $length;
      
        // ... and change the Transfere-Encoding.
        $chunked['HEADER_FIELDS']['Transfer-Encoding'] = 'token';
  
        return $chunked;
    }  
        
    /**
     * @public
     *
     * Returns the HTTP-header like a GET or POST reply but without the file
     *
     * @param String $uri The uri to send the request to.
     * @param Array $params The url parameters.
     * @param Array $cookies The cookies.
     * @param String $user The user's name.
     * @param String $password The user's password.
     * @return The data reponse array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     * Version 0.2, 04.04.2010 (Now using arrays to create parameter-lists)<br />
     * Version 0.3, 06.04.2010 (Added proxy support)<br />
     */
    public function head($uri='/', $params=array(), $cookies=array(), $user=null, $password=null) {
        // Log onto the server, ...
        $handle = $this->login();
      
        // ... and create the authorization-string.
        $auth = $this->auth($user, $password);
      
        // Now prepare the uri for the request, ...
        if (empty($uri) || ($uri{0}!='/')) {
            $uri = '/' . $uri;
        }
            
        // ... check if parameters are given ...
        if ( !empty($params) ) {
            // ... and prepare them for the request.
            $paramData = '?';
            foreach($params as $key => $val) {
                $paramData .= $key . '=' . $val . '&';
            }
        }
      
        else {
            $paramData = '';
        }
      
        // Check if cookies are given ...
        if ( !empty($cookies) ) {
            // ... and prepare them for the request.
            $cookieData = "Cookie: ";
            foreach($cookies as $key => $val) {
                $cookieData .= $key . '=' . $val . ';';
            }
            $cookieData .= "\r\n";
        }
      
        else {
            $cookieData = '';
        }
      
        // Now prepare the host-data, ...
        $host = $this->host;

        // ... add the port ...
        if ($this->port != 80) {
            $host .= ':'.$this->port;
        }

        // ... and the right prefix if needed ...
        if ($this->proxy!=false) {
            if ($this->ssl==true) {
                $uri='https://'.$host.$uri;
            }

            else {
                $uri='http://'.$host.$uri;
            }
        }
      
        // ... and send the request to the server.
        $this->cmd($handle, "HEAD $uri"."$paramData HTTP/1.1\r\n");
        $this->cmd($handle, "Host: $host\r\n".$cookieData.$auth);
        $this->cmd($handle, "User-Agent: ".$this->referrer."\r\n");
        $this->cmd($handle, "Connection: close\r\n\r\n");
      
        // Finally get the server's response, ...
        $reply = $this->readSock($handle);
      
        // ... close the connection to the server ...
        $this->logout($handle);
      
        // ... and create the result array.
        $data = $this->divideReply($reply);
      
        return $data;
    }
        
    /**
     * @public
     *
     * Returns the HTTP-header and the output of the file.
     *
     * @param String $uri The uri to send the request to.
     * @param Array $params The url parameters.
     * @param Array $cookies The cookies.
     * @param String $user The user's name.
     * @param String $password The user's password.
     * @return The data reponse array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     * Version 0.2, 04.04.2010 (Now using arrays to create parameter-lists)<br />
     * Version 0.3, 06.04.2010 (Added proxy support)<br />
     */
    public function get($uri='/', $params=array(), $cookies=array(), $user=null, $password=null) {
        // Log onto the server, ...
        $handle = $this->login();
      
        // ... and create the authorization-string.
        $auth = $this->auth($user, $password);
      
        // Now prepare the uri for the request, ...
        if (empty($uri) || ($uri{0}!='/')) {
            $uri = '/' . $uri;
        }
            
        // ... check if parameters are given ...
        if ( !empty($params) ) {
            // ... and prepare them for the request.
            $paramData = '?';
            foreach($params as $key => $val) {
                $paramData .= $key . '=' . $val . '&';
            }
        }
      
        else {
            $paramData = '';
        }
      
        // Check if cookies are given ...
        if ( !empty($cookies) ) {
            // ... and prepare them for the request.
            $cookieData = "Cookie: ";
            foreach($cookies as $key => $val) {
                $cookieData .= $key . '=' . $val . ';';
            }
            $cookieData .= "\r\n";
        }
      
        else {
            $cookieData = '';
        }
      
        // Now prepare the host-data, ...
        $host = $this->host;

        // ... add the port ...
        if ($this->port != 80) {
            $host .= ':'.$this->port;
        }

        // ... and the right prefix if needed ...
        if ($this->proxy!=false) {
            if ($this->ssl==true) {
                $uri='https://'.$host.$uri;
            }

            else {
                $uri='http://'.$host.$uri;
            }
        }
      
        // ... and send the request to the server.
        $this->cmd($handle, "GET $uri"."$paramData HTTP/1.1\r\n");
        $this->cmd($handle, "Host: $host\r\n".$cookieData.$auth);
        $this->cmd($handle, "User-Agent: ".$this->referrer."\r\n");
        $this->cmd($handle, "Connection: close\r\n\r\n");
      
        // Finally get the server's response, ...
        $reply = $this->readSock($handle);
      
        // ... close the connection to the server ...
        $this->logout($handle);
      
        // ... and create the result array.
        $data = $this->divideReply($reply);
      
        return $data;
    }

    /**
     * @public
     *
     * Returns the HTTP-header and the output of the file.
     *
     * @param String $uri The uri to send the request to.
     * @param Array $params The url parameters.
     * @param Array $cookies The cookies.
    * @param Array $fileparams The parameters of a file handle.
    * @param Array $mimes The mime-types that belong to the file handles.
     * @param String $user The user's name.
     * @param String $password The user's password.
     * @return The data reponse array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     * Version 0.2, 04.04.2010 (Now using arrays to create parameter-lists)<br />
     * Version 0.3, 06.04.2010 (Added proxy support)<br />
     */
    public function post($uri='/', $params=array(), $cookies=array(), $fileparams=array(), $mimes=array(), $user=null,$password=null) {
        // Log onto the server, ...
        $handle = $this->login();
      
        // ... and create the authorization-string.
        $auth = $this->auth($user, $password);
      
        // Now prepare the uri for the request, ...
        if (empty($uri) || ($uri{0}!='/')) {
            $uri = '/' . $uri;
        }
            
        // ... check if parameters are given ...
        if ( !empty($params) ) {
            // ... and prepare them for the request.
            $paramData = '?';
            foreach($params as $key => $val) {
                $paramData .= $key . '=' . $val . '&';
            }
        }
      
        else {
            $paramData = '';
        }
      
        // Check if cookies are given ...
        if ( !empty($cookies) ) {
            // ... and prepare them for the request.
            $cookieData = "Cookie: ";
            foreach($cookies as $key => $val) {
                $cookieData .= $key . '=' . $val . ';';
            }
            $cookieData .= "\r\n";
        }
      
        else {
            $cookieData = '';
        }
      
        // Now prepare the host-data, ...
        $host = $this->host;

        // ... add the port if needed ...
        if ($this->port != 80) {
            $host .= ':'.$this->port;
        }

        // ... and add the right prefix.
        if ($this->proxy!=false) {
            if ($this->ssl==true) {
                $uri='https://'.$host.$uri;
            }

            else {
                $uri='http://'.$host.$uri;
            }
        }

        // Check if file parameters are given, ...
        if ( empty($fileparams) ) {
            // ... check if post parameters are given.
            if ( !empty($params) ) {
                // If post parameters are given, get the content length, ...
                $length = strlen($paramData);
            
                // ... and send the request to the server.
                $this->cmd($handle, "POST ".$uri." HTTP/1.1\r\n");
                $this->cmd($handle, "Host: $host\r\n".$cookieData.$auth);
                $this->cmd($handle, "User-Agent: ".$this->referrer."\r\n");
                $this->cmd($handle, "Connection: close\r\n");
                $this->cmd($handle, "Content-Type: application/x-www-form-urlencoded\r\n");
                $this->cmd($handle, "Content-Length: ".$length."\r\n\r\n".$paramData);
            }

            else {
                // Send the request without parameters.
                $this->cmd($handle, "POST ".$uri." HTTP/1.1\r\n");
                $this->cmd($handle, "Host: $host\r\n".$cookieData.$auth);
                $this->cmd($handle, "User-Agent: ".$this->referrer."\r\n");
                $this->cmd($handle, "Connection: close\r\n");
            }
        }
      
      else {
            // If there are file parameters given, fill empty mimetype-spaces with standard-mimetype.
            while (count($mimeparam) < count($fparam)) {
                $mimes[]='application/octet-stream';
            }        
        
            // Create a boundary, that could be used to seperate the different blocks holding the parameters.
            $boundary = md5(uniqid());

            // Then initialize the content block, ...
            $content = '';        
        
            // ... start a counter ...
            $key = 0;

            // ... and create a content block for each file.
            foreach($fileparams as $name => $fname) {
                $fp = fopen($fname, 'r');
                $fcontent = fread($fp, filesize($fname));
                $content .= '--'.$boundary."\r\n";
                $content .= 'Content-Disposition: form-data; name="'.$name.'"; filename="'.$fname.'"'."\r\n";
                $content .= 'Content-Type: '.$mimes[$key]."\r\n\r\n";
                $content .= $filecontent."\r\n";
  
                $key++;
            }
        
            // The prepare the blocks for the post parameters.
         foreach($params as $name => $value) {
                $content .= '--'.$boundary."\r\n";
                $content .= 'Content-Disposition: form-data; name="'.$name.'"'."\r\n\r\n";
            
                if (!empty($value)) {
                    $content .= $value."\r\n";
                }
            }
        
            $content .= '--'.$boundary."--\r\n";
        
            // Finally get the length of the content block, ...
            $length = strlen($content);
        
            //  ... send the standard data ...
            $this->cmd($handle, "POST ".$uri." HTTP/1.1\r\n");
            $this->cmd($handle, "Host: $host\r\n".$cookieData.$auth);
            $this->cmd($handle, "User-Agent: ".$this->referrer."\r\n");
            $this->cmd($handle, "Connection: close\r\n");                  
                  
            // ... and after that send the the parameters and file-parameters.
            $this->cmd($handle, "Content-Type: multipart/form-data; boundary=".$boundary."\r\n");
            $this->cmd($handle, "Content-Length: ".$length."\r\n\r\n");
            $this->cmd($handle, $content);
        }
      
        // After all get the server's response, ...
        $reply = $this->readSock($handle);
      
        // ... close the connection to the server ...
        $this->logout($handle);
      
        // ... and create the result array.
        $data = $this->divideReply($reply);
      
        return $data;
    }
            
    /**
     * @private
     *
     * Saves all request and responses to and from the server.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    private function log($str) {
        // Check if data should be logged ...
        if($this->logStats) {
            // ... and log them if needed.
            $this->log .= "$str<br />";
        }
    }
            
    /**
     * @public
     *
     * Returns the currently logged data.
     *
     * @return The logging output.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 25.01.2008<br />
     */
    public function __toString() {
        return $this->log;
    }
}

?>


ICQ - Ein Anwendungsbeispiel

Nun, da die Klasse fertig gestellt ist, wird es Zeit für ein Anwendungsbeispiel und wie könnte es anders sein, wir werden meinen ICQ-Onlinestatus abfragen. Dabei dürfen wir aber nicht vergessen den kompletten Code, der das Arbeiten mit der Klasse HttpConnect regelt, in einen try...catch-Block zu setzen, da sonst eventuell geworfene Exceptions nicht aufgefangen werden. Wir includen also die Klasse, instanzieren sie und stellen eine Verbindung zum Host status.icq.com über den Port 80 her. Danach senden wir einen HEAD-Befehl mit der Adresse zur betreffenden Datei, sowie dem ICQ-Parameter. In unserem Fall ist ein HTTP-HEAD-Request vollkommen ausreichend, da wir ja wissen, dass wir, je nach Onlinestatus des abgefragten Users, auf eine andere URL weitergeleitet werden. Wir brauchen also nur den Location-Header auswerten und eine entsprechende Ausgabe machen. Werden wir auf die Datei online0.gif weitergeleitet, ist der User offline, bei online1.gif ist er online und bei online2.gif N/A. Der Quelltext ist aber auch eigentlich selbst erklärend.

Code:

<?php

try {
    // Include the http request handler ...
    require_once("HttpConnect.php");

    // ... and create an instance.
    $http = new HttpConnect();

    // Now connect to the icq.com login server ...
    $http->connect("wwp.icq.com", 80, false, false, 443, true);

    // ... and send the data to the script.
    $data = $http->head("/scripts/online.dll", array("icq" => "237076093", "img" => "5"));

    // Now get the right online state ...
    if ( strstr($data['HEADER_FIELDS']['Location'], 'online0') ) {
        $status = "<img src=\"http://status.icq.com/5/online0.gif\" alt=\"offline\" \>Avedo is offline.";
    }
  
    elseif (strstr($data['HEADER_FIELDS']['Location'], 'online1')) {
        $status = "<img src=\"http://status.icq.com/5/online1.gif\" alt=\"online\" \>Avedo is online.";
    }

    else {
        $status = "<img src=\"http://status.icq.com/5/online2.gif\" alt=\"N/A\" \>Avedo is N/A.";
    }
  
    // ... and print it.
    echo $status;
}

catch(Exception $e) {
    echo $e->getMessage();
}

?>


Fragen und Antworten

Fragen und Antworten findet ihr selbstverständlich bei uns im Forum. Ich möchte euch auch darum bitten, keine Fragen an mich persönlich zu senden, aus dem einfachen Grund, dass andere Menschen dann nichts von den gegebenen Antworten haben. Zudem können euch im Forum auch viele andere Leute, die echt Ahnung haben, helfen. Natürlich freue ich mich über ein kurzes Feedback zu diesem Tutorial. Ich bedanke mich für eure Aufmerksamkeit und hoffe, dass euch mein kurzer Exkurs gefallen hat.

Liebe Grüsse,

Andreas

http://www.avedo.net

>> Allgemeine Fragen oder Probleme mit dem Tutorial? Hier gehts zum Forum!

Impressum / Datenschutzerklärung          © der-Webdesigner.net 2002 - 2011           top ▲