programmieren

Link-Validator in PHP3

In den "Gadget"-Ausgaben 40 und 42 wurde ein rudimentärer Link-Validator in Perl geschrieben. Dieser basierte auf der Idee, dass auf eine besondere Veranlassung (den Programmaufruf) hin eine Liste mit WWW-Verweisen Eintrag für Eintrag auf ihre Validität hin überprüft werden sollte. Der Nachteil dieses Ansatzes liegt auf der Hand. Eine solche Überprüfung muss vom Anbieter der Link-Liste in regelmäßigen Abständen durchgeführt werden (so er denn keine Möglichkeit hat, den Programmaufruf in bestimmten Intervallen automatisch erfolgen zu lassen), der Pflegeaufwand steigt erheblich an. Dieses auf einer zentralen Kontrollinstanz beruhende Konzept erscheint angesichts der anarchischen, dezentralen Struktur des WWW beinahe schon anachronistisch. Wie wäre es also, wenn statt des Anbieters der Anwender selbst die Validität des Verweises prüfen würde ? Sicher, im Prinzip entspricht das dem Zustand ohne Link-Validierung. Klickt der Nutzer auf einen "toten" Link, so bekommt er eine Fehlermeldung angezeigt und wird möglicherweise den Anbieter der Link-Liste darauf hinweisen. Das ist jedoch für beide Seiten mit Nachteilen verbunden: der Nutzer muss sich über eine fehlerhafte Link-Liste ärgern und der Anbieter kann nicht sicher sein, dass der Nutzer den Fehler meldet - die Erfahrung lehrt im Gegenteil, dass dies regelmäßig nicht geschehen wird. Genau diese Situation inspirierte dann ja auch zur Programmierung des Link-Validators in Perl / CGI. Wenn man diese Überprüfung jedoch nicht zu einem bestimmten Zeitpunkt durch den Anbieter der Link-Liste durchführen lässt, sondern statt dessen automatisch beim Anklicken eines Links durch den Nutzer die Validität des Link überprüft, könnte man einerseits eventuelle Verweise ins Leere zwar nicht vermeiden, sie jedoch abfangen und den Nutzer im Rahmen der vom genutzten Angebot her bekannten grafischen Umgebung wieder zurück zur Link-Liste führen. Andererseits ließe sich dabei auch der Anbieter der Liste informieren, so dass dieser den fehlerhaften Verweis entweder korrigieren oder auch entfernen kann. Für diese Aufgabenstellung empfiehlt sich die Preprocessor-Sprache PHP3, von der grundlegende Kenntnisse im Folgenden vorausgesetzt werden. (Der Link-Validator lässt sich allerdings natürlich auch in Perl / CGI realisieren.)

Damit dieses Konzept funktioniert, dürfen die Links nicht auf die eigentlich verlinkte Seite verweisen. Statt dessen muss bei dem Klick auf einen Link der Liste der Validator aktiviert werden. Dabei muss die URL, die geprüft und dann aufgerufen werden soll, übergeben werden. Wenn wir annehmen, dass das Dokument, welches die Prüfung vornehmen wird, "launch.phtml" heißen wird, müsste an Stelle der Zeile

   <a href="http://www.amigagadget.de">AmigaGadget</a>
  

somit die Zeile

   <a href="launch.phtml?launchurl=http://www.amigagadget.de">AmigaGadget</a>
  

treten. Die URL "http://www.amigagadget.de" würde dabei in der CGI-Variable "launchurl" übergeben werden. Das ganze hat jedoch noch einen bedeutsamen Fehler. Da in der CGI-Umgebung einer URL nicht alle Zeichen zulässig sind, kann man den Link nicht volständig im Klartext übergeben. Zum Glück stellt PHP3 jedoch eine interne Funktion ("urlencode") zur Verfügung, die eine Zeichenkette in die gültige URL-CGI-Notation konvertiert. Die Zeile heißt somit korrekt

   <a href="launch.phtml?launchurl=<? print urlencode ("http://www.amigagadget.de") ?>">AmigaGadget</a>
  

Innerhalb des HTML-Codes wird also ein PHP3-Befehl aufgerufen, der das Ergebnis der Funktion "urlencode" mit der zu verlinkenden URL als Parameter ausgibt. Im Gegenzug muss nun natürlich auch die Datei "launch.phtml" die übergebene URL erst mit "urldecode" zurückkonvertieren, bevor sie validiert und gegebenfalls angesprungen werden kann.

Damit stellt sich aber nun die Frage, was es mit der Datei "launch.phtml" auf sich hat. Um das Ganze möglichst kompakt zu halten, empfiehlt es sich, die Link-Liste und den Validator in einem einzigen Dokument zu realisieren. "Launch.phtml" ist also nicht nur die Datei, die zur Überprüfung eines Links aufgerufen wird, sie ist zugleich die Datei, die die Link-Liste enthält. Die Weichenstellung, ob ein Link überprüft oder die Link-Liste ausgegeben werden soll, erfolgt dabei einfach mit einer if-Abfrage zu Beginn der PHP3-Datei. Die Struktur der "launch.phtml" stellt sich also folgendermaßen dar:

  <?
   if (isset($launchurl))
   {
    # Link-Validierung
   }
   else
   {
    # Ausgabe der Link-Liste
   }
  ?>
  

Die eigentliche Überprüfung des Links erfolgt dann ganz ähnlich wie in den schon bekannten Perl-Validatoren. Allerdings steht unter PHP3 kein Zugriff auf die anwenderfreundliche "LWP Library" zur Verfügung. Statt dessen muss man den Client-Zugriff selbst im Detail durchführen. Ein solcher Client-Zugriff besteht aus dem Aufbau der HTTP-Verbindung und der Absendung eines HTTP-Requests. Eine Internet-Verbindung wird unter PHP3 mit dem Befehl "fsockopen" geöffnet, die Übergabeparameter sind die Domain (oder die IP-Adresse) des zu kontaktierenden Servers ("Host"), der Port, über den der Verbindungsaufbau hergestellt werden soll, Zeiger auf Variablen für eine eventuelle Fehlernummer und die dazugehörige Fehlermeldung sowie die Angabe, wie viele Sekunden auf eine serverseitige Reaktion gewartet werden soll. Im Überblick:

  int fsockopen (string hostname, int port, int [errno], string [errstr], double [timeout]);
  

Angesichts dieser Syntax drängt sich ein Problem auf - wir haben zwar die URL, nicht jedoch den den Hostnamen. Glücklicherweise bietet PHP3 hierfür jedoch eine sehr mächtige Funktion an, die URLs in ihre Bestandteile zerlegt, ohne dass man sich selbst mit regulären Ausdrücken oder ähnlichen Monstrositäten herumschlagen müsste. Statt dessen genügt ein Aufruf der Funktion "parse_url" und die Elemente der als Parameter übergebenen Datei werden aufbereitet in einem assoziativen Array abgelegt. So befindet sich der Hostname ("www.amigagadget.de") der URL "http://www.amigagadget.de/index.html" nach dem Aufruf

    $wwwzeug = parse_url (urldecode($launchurl));
  

in der Variable $wwwzeug[host]. Neben "host" gibt es noch die folgenden Indizes, deren Felder durch parse_url bei Vorhandensein der entsprechenden URL-Bestandteile mit dem jeweiligen Wert initialisiert werden:

      - scheme
      - port
      - user
      - pass
      - path
      - query
      - fragment
  

Damit steht dem "fsockopen"-Aufruf aber nichts mehr im Wege, wenn man weiß, dass ein HTTP-Request in der Regel auf Port 80 erfolgt. (Korrekterweise müsste man eigentlich auch $wwwzeug[port] (und $wwwzeug[scheme]) auswerten. Um das Beispielprogramm nicht unnötig mit Ballast zu beladen, soll nachfolgend jedoch davon ausgegangen werden, dass stets das HTTP-Protokoll auf Port 80 verwendet wird.) Die beiden Programmzeilen lauten folglich:

    $wwwzeug = parse_url (urldecode($launchurl));
    $wwwhandle = fsockopen ($wwwzeug[host], 80, &$errno, &$errdescr, 30);
  

Ist $wwwhandle "false", dann ist der Verbindungsaufbau fehlgeschlagen (etwa weil es den Host nicht gibt). Andernfalls können wir uns nun daran machen, den HTTP-Request abzusetzen. Dabei wird genauso zweistufig verfahren wie im zweiten Perl-Validator-Skript - zunächst wird ein HEAD-Request versucht, und erst wenn dieser fehlschlägt, wird auch ein GET-Request abgesetzt, der im Erfolgsfall ja das komplette HTML-Dokument (oder gegebenenfalls natürlich auch ein Dokument in einem anderen Format) anfordert. Für die erläuternde Darstellung reicht jedoch die Behandlung des HEAD-Requests.

Ein HTTP-Request besteht aus dem Kommando ("HEAD"), der genauen Pfadangabe der Datei, auf die das Kommando angewendet werden soll, der Kennung der HTTP-Version sowie zwei schließenden Carriage-Return ("\r") / Newline ("\n")-Kombinationen. Eine HEAD-Anfrage auf http://www.amigagagdet.de/index.html sähe demnach so aus:

      HEAD /index.html HTTP/1.0\r\n\r\n
  

Da der Zugriff auf eine einmal initialisierte Internet-Verbindung in PHP3 genauso funktioniert wie ein Zugriff auf eine Datei, schreibt man diese Anfrage einfach via "fputs" in $wwwhandle. Die Daten, die der Server zurückliefert, kann man dann mit "fgets" auslesen und auswerten. Dabei steht bei einem erfolgreichen HTTP-Request in der dritten Zeile (nur) das Wort "OK". Ist ein Fehler aufgetreten, so findet man in der zweiten Zeile eine den Fehler genauer spezifizierende Fehlernummer und in der vierten Zeile die Fehlermeldung im Klartext. Im Prinzip sieht die ganze Client-Routine somit so aus:

     fputs ($wwwhandle, "HEAD " . $wwwzeug[path] . " HTTP/1.0\r\n\r\n");
     $wwwinput = fgets($wwwhandle,4096);
     $kopfzeilen = explode (" ", $wwwinput);
     fclose ($wwwhandle);
  

Ein nachfolgendes

     if (!strcmp ($kopfzeilen[2], "OK"))
  

stellt dann die Weiche für den Fall, dass der HTTP-Request kein "OK" zurücklieferte.

Damit ist das erforderliche Rüstzeug auch schon beinahe vorhanden. Nur noch eine Kleinigkeit muss berücksichtigt werden - bei einer URL ohne Pfadangabe ist auch $wwwzeug[path] leer, der HTTP-Request sähe dann so aus:

      HEAD  HTTP/1.0\r\n\r\n
  

Ein solcher Request führt jedoch zu einer Fehlermeldung. Sinnvollerweise muss man daher in einem solchen Fall vor Formulierung des HTTP-Requests die Pfadangabe überprüfen und die entsprechende Variable gegebenenfalls mit "/" initialisieren:

    if (empty ($wwwzeug[path]))
    {
     $wwwzeug[path] = "/";
    }
  

Damit sind dann aber auch schon die meisten programmiertechnisch erwähnenswerten Programmteile entwickelt. Es fehlt nur noch die Funktion, die im Falle eines unkorrigierbaren Fehlers eine Mail an den Anbieter der Link-Liste absetzt. Hier bietet PHP3 wieder eine sehr praktische Funktion an: "mail". Dieser übergibt man die e-Mail-Adresse des Empfängers (hier also die Adresse des Anbieters der Link-Liste), einen Betreff, den Text, der im Körper der Mail stehen soll sowie etwa gewünschte Zusatzkopfzeilen, wobei hier insbesondere eine Absenderangabe ("From:") in Betracht kommt (auch wenn diese in unserem Fall natürlich keinen echten Informationswert hat, da "Absender" das Skript selbst ist). Der Funktionsaufruf könnte, wieder unter Berücksichtigung des für "GaMa" erforderlichen Zeilenumbruchs, also folgendermaßen aussehen:

     mail ("urltest@amigagadget.de", "Fehlermeldung", "Achtung: Es gab bei Aufruf
     der URL\n" . urldecode ($launchurl) . "\nfolgenden Fehler:\n" .
     $fehlerbeschreibung . " (" . $fehlernummer . ")", "From:
     autocheck@amigagadget.de");
  

War die Validitätsprüfung hingegen erfolgreich, so muss die aus Sicht des Nutzers aufgerufene Seite ohne weitere Verzögerungen angesprungen werden, damit der Nutzer den zwischengeschalteten Link-Validator gar nicht zur Kenntnis nehmen muss. Dazu bietet PHP3 die Möglichkeit, HTTP-Headerinformationen zu versenden. Damit wird ein automatischer "Redirect" auf eine andere URL möglich:

     header ("Location: " . urldecode($launchurl));
  

Voraussetzung ist allerdings, dass zuvor noch kein einziges Byte HTML-Quelltext übertragen wurde. Fügt man die vorgestellten Programmteile nun zusammen und ergänzt sie um das nötige "Füllwerk" (inbesondere die Fehlermeldung und die Link-Liste), so bekommt man folgendes Programm:

  <?
   if (isset($launchurl))
   {
    $wwwzeug = parse_url (urldecode($launchurl));
    if (empty ($wwwzeug[path]))
    {
     $wwwzeug[path] = "/";
    }
    $wwwhandle = fsockopen ($wwwzeug[host], 80, &$errno, &$errdescr, 30);
    if (!$wwwhandle)
    {
     $fehlernummer = 666;
     $fehlerbeschreibung = "Kann keine WWW-Verbindung herstellen!";
    }
    else
    {
     fputs ($wwwhandle, "HEAD " . $wwwzeug[path] . " HTTP/1.0\r\n\r\n");
     $wwwinput = fgets($wwwhandle,4096);
     $kopfzeilen = explode (" ", $wwwinput);
     fclose ($wwwhandle);
     if (!strcmp ($kopfzeilen[2], "OK"))
     {
      fputs ($wwwhandle, "GET " . $wwwzeug[path] . " HTTP/1.0\r\n\r\n");
      $wwwinput = fgets($wwwhandle,4096);
      $kopfzeilen = explode (" ", $wwwinput);
      fclose ($wwwhandle);
      if (!strcmp ($kopfzeilen[2], "OK"))
      {
       $fehlerbeschreibung = $kopfzeilen[3];
       $fehlernummer = $kopfzeilen[1];
      }
     }
    }

    if ($fehlernummer == 0)
    {
     header ("Location: " . urldecode($launchurl));
    }
    else
    {
  ?>
  <html>
   <body>
    <h2>Fehlermeldung</h2>
    <p>
     Die von Ihnen aufgerufene Adresse scheint fehlerhaft zu sein.
     Die Verwalter der Link-Liste sind bereits informiert
     und werden die Liste umgehend anpassen, sofern dies erforderlich ist.
    </p>
    <p>
     Falls Sie den Link dennoch ausprobieren m&ouml;chten, klicken Sie
     bitte <a href="<? print urldecode ($launchurl) ?>">hier</a>.
    </p>
    <p align="center">
     <a href="launch.phtml">Zur&uuml;ck</a>
    </p>
   </body>
  </html>
  <?
     mail ("urltest@amigagadget.de", "Fehlermeldung", "Achtung: Es gab bei Aufruf
     der URL\n" . urldecode ($launchurl) . "\nfolgenden Fehler:\n" .
     $fehlerbeschreibung . " (" . $fehlernummer . ")", "From:
     autocheck@amigagadget.de");
    }
   }
   else
   {
  ?>
  <html>
   <body>
    <ul>
     <li><a href="launch.phtml?launchurl=<? print
         urlencode ("http://www.amiga.com") ?>">Amiga</a>
     </li>
     <li><a href="launch.phtml?launchurl=<? print
         urlencode ("http://www.amigagadget.de") ?>">AmigaGadget</a>
     </li>
     <li><a href="launch.phtml?launchurl=<? print
         urlencode ("http://www.amiga-news.de") ?>">Amiga-News.de</a>
     </li>
     <li><a href="launch.phtml?launchurl=<? print
         urlencode ("http://dieseurlgibtesnicht.ug") ?>">Hier gibt es
         eine Fehlermeldung</a>
     </li>
    </ul>
   </body>
  </html>
  <?
   }
  ?>
  

Selbstverständlich ist hier noch einiges an Optimierung möglich. Insbesondere bietet es sich an, die Verweise in einem Array, einer externen Datei oder auch in einer MySQL-Tabelle zu speichern und die Link-Liste dann einfach über eine For-Next-Schleife zu erzeugen. Um die Lösung der hier gegebenen Aufgabenstellung zu demonstrieren, mag es jedoch mit dieser unoptimierten Fassung für heute sein Bewenden haben.

(c) 2000 by Andreas Neumann

Zurück