Teil von SELFHTML aktuell Teil von Artikel Teil von PHP

PHP:
Existenzprüfung externer HTTP-Resourcen

nach unten Alexander Brock
nach unten Hinweise zum Thema
nach unten Funktion mit fopen()
nach unten Funktion mit fsockopen()
nach unten Weiterführende Links

Alexander Brock

E-Mail: E-Mail a.brock@hhv-rheinklang.de
Homepage-URL: deutschsprachige Seite http://alexanderbrock.de/

Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!

nach obennach unten

Hinweise zum Thema

Bei dynamischen Webseiten taucht häufiger das Problem auf, dass externe URIs nicht nur auf ihre formale Gültigkeit, sondern auch auf die Existenz einer Resource dahinter überprüft werden müssen. In PHP gibt es zwei eingebaute Funktionen, mit denen dies möglich ist: fopen() und fsockopen(). Im folgenden sollen die Vor- und Nachteile beider Varianten erläutert werden.

nach obennach unten

Funktion mit fopen()

function http_test_existance($url) {
 return (($fp = @fopen($url, 'r')) === false) ? false : @fclose($fp);
}

Erläuterung:

Bei dieser Methode wird versucht, den URL direkt an die Funktion fopen() zu übergeben. Falls die Funktion nicht fehlschlägt, wird der Zeiger wieder geschlossen. Im Erfolgsfall wird true zurückgegeben, im Fehlerfall false.

Diese Methode ist zwar relativ einfach, hat aber ein paar gravierende Nachteile:

Wenn Ihnen diese Methode dennoch reicht können Sie sie wie folgt verwenden:

if (http_test_existance('http://example.org/')) {
 echo 'Test erfolgreich.';
}
else {
 echo 'Test fehlgeschlagen.';
}

nach obennach unten

Funktion mit fsockopen()

function http_test_existance(
 $url,
 $timeout = 10
) {
 $timeout = (int)round($timeout/2+0.00000000001);
 $return = array();

### 1 ###
 $inf = parse_url($url);

 if (!isset($inf['scheme']) or $inf['scheme'] !== 'http') return array('status' => -1);
 if (!isset($inf['host'])) return array('status' => -2);
 $host = $inf['host'];

 if (!isset($inf['path'])) return array('status' => -3);
 $path = $inf['path'];
 if (isset($inf['query'])) $path .= '?'.$inf['query'];

 if (isset($inf['port'])) $port = $inf['port'];
 else $port = 80;

### 2 ###
 $pointer = fsockopen($host, $port, $errno, $errstr, $timeout);
 if (!$pointer) return array('status' => -4, 'errstr' => $errstr, 'errno' => $errno);
 socket_set_timeout($pointer, $timeout);

### 3 ###
 $head =
  'HEAD '.$path.' HTTP/1.1'."\r\n".
  'Host: '.$host."\r\n";

 if (isset($inf['user']))
  $head .= 'Authorization: Basic '.
   base64_encode($inf['user'].':'.(isset($inf['pass']) ? $inf['pass'] : ''))."\r\n";
 if (func_num_args() > 2) {
  for ($i = 2; $i < func_num_args(); $i++) {
   $arg = func_get_arg($i);
   if (
    strpos($arg, ':') !== false and
    strpos($arg, "\r") === false and
    strpos($arg, "\n") === false
   ) {
    $head .= $arg."\r\n";
   }
  }
 }
 else $head .= 
  'User-Agent: Selflinkchecker 1.0 (http://aktuell.selfhtml.org/artikel/php/existenz/)'."\r\n";

 $head .=
 'Connection: close'."\r\n"."\r\n";

### 4 ###
 fputs($pointer, $head);

 $response = '';

 $status = socket_get_status($pointer);
 while (!$status['timed_out'] && !$status['eof']) {
  $response .= fgets($pointer);
  $status = socket_get_status($pointer);
 }
 fclose($pointer);
 if ($status['timed_out']) {
  return array('status' => -5, '_request' => $head);
 }

### 5 ###
 $res = str_replace("\r\n", "\n", $response);
 $res = str_replace("\r", "\n", $res);
 $res = str_replace("\t", ' ', $res);

 $ares = explode("\n", $res);
 $first_line = explode(' ', array_shift($ares), 3);

 $return['status'] = trim($first_line[1]);
 $return['reason'] = trim($first_line[2]);

 foreach ($ares as $line) {
  $temp = explode(':', $line, 2);
  if (isset($temp[0]) and isset($temp[1])) {
   $return[strtolower(trim($temp[0]))] = trim($temp[1]);
  }
 }
 $return['_response'] = $response;
 $return['_request'] = $head;

 return $return;

}

Erläuterung:

Bei diesem Ansatz wird ein HTTP-Request manuell abgesetzt, anstelle dies von PHP durchführen zu lassen. Somit kann man Fehlersituationen detaillierter erkennen und hat mehr Kontrolle über die gesendeten Daten. Dazu wird die Funktion fsockopen() verwendet. Diese baut hier eine TCP-Verbindung zum Webserver auf. Um den Hostnamen des fremden Servers in Erfahrung zu bringen, wird der URL mit der Funktion parse_url zerlegt.

Die Funktion prüft dann ansatzweise die Korrektheit der URL und gibt im Fehlerfall ein assoziatives Array zurück:

In Abschnitt 2 wird die Verbindung zum Server hergestellt und mit socket_set_timeout die Zeitbeschränkung für das Lesen der Server-Antwort eingestellt. Wenn ersteres fehlschlägt wird wieder ein Array zurückgegeben:

 array(
  'status' => -4
  'errno'  => # (String)  Nummer des Fehlers, die fsockopen in den
              #            dritten Parameter geschrieben hat.
  'errstr' => # (Integer) Fehlermeldung, die fsockopen in den
              #            vierten Parameter geschrieben hat.
 )

In Abschnitt 3 wird der eigentliche HTTP-Request zusammengebaut. Er besteht mindestens aus vier Zeilen:

HEAD /url HTTP/1.1
Host: example.org
Connection: close
Leerzeile

Die Parameter der Funktion spielen hier eine besondere Rolle:

In Abschnitt 4 wird der erzeugte HTTP-Request an den Server geschickt. Dann wird die Server-Antwort zeilenweise eingelesen, wobei nach jeder Zeile überprüft wird, ob eine Zeitüberschreitung aufgetreten ist. In diesem Fall bricht die Funktion ab und gibt wieder mal ein Array zurück:

array(
 'status' => -5,
 '_request' => # (String) der gesendete HTTP-Request
)

Wenn bis dahin alles erfolgreich war wird im fünften Abschnitt die Antwort des Servers in ein handliches Array zerlegt. Dazu werden alle mäglichen Zeilenumbrüche in den Unix-Zeilenumbruch umgewandelt und die Antwort an letzterem in ein Array zerlegt. Das erste Element enthält dann die höchste HTTP-Version, die der Server unterstützt (HTTP/1.0 oder HTTP/1.1), den Status-Code (z.B. 200) und die Reason-Phrase (z.B. OK, Not Found, Moved Permanently etc.). Die beiden letzten Angaben werden folgendermaßen in das Array, das am Ende zurückgegeben wird geschrieben:

 $return['status'] = # (String) Status-Code (z.B. 200)
 $return['reason'] = # (String) Reason-Phrase (z.B. OK, Not Found, Moved Permanently etc.)

Alle weiteren Zeilen werden am Doppelpunkt geteilt und jeweils der Teil vor dem Doppelpunkt in Kleinschrift als Schlüssel für den Teil nach dem Doppelpunkt in das Rückgabe-Array geschrieben. Beide Zeichenketten werden mittels trim() von eventuell vorhandenen Leerzeichen / Tabulatoren an den Rändern befreit.

Zuletzt werden noch der kompletter gesendete HTTP-Request mit dem Schlüssel _request und die komplette unveränderte Serverantwort mit dem Schlüssel _response in das Rückgabe-Array geschrieben.

Anwendungsbeispiele

Zunächst ein ganz einfaches Anwendungsbeispiel:

$result = http_test_existance('http://selfhtml.org/');
print_r($result);

Die Ausgabe des Scripts sollte in etwa so aussehen:

Array
(
    [status] => 200
    [reason] => OK
    [date] => Fri, 11 Nov 2005 21:27:42 GMT
    [server] => Apache/1.3.33 (Unix)  (Gentoo/Linux) PHP/4.4.0-gentoo-r1 mod_auth_ldap/2.4.2 mod_ssl/2.8.24 OpenSSL/0.9.7e mod_gzip/1.3.26.1a
    [last-modified] => Fri, 25 Mar 2005 07:37:20 GMT
    [etag] => "24f636-4800-4243bfb0"
    [accept-ranges] => bytes
    [content-length] => 18432
    [connection] => close
    [content-type] => text/html
    [_response] => HTTP/1.1 200 OK
Date: Fri, 11 Nov 2005 21:27:42 GMT
Server: Apache/1.3.33 (Unix)  (Gentoo/Linux) PHP/4.4.0-gentoo-r1 mod_auth_ldap/2.4.2 mod_ssl/2.8.24 OpenSSL/0.9.7e mod_gzip/1.3.26.1a
Last-Modified: Fri, 25 Mar 2005 07:37:20 GMT
ETag: "24f636-4800-4243bfb0"
Accept-Ranges: bytes
Content-Length: 18432
Connection: close
Content-Type: text/html


    [_request] => HEAD / HTTP/1.1
Host: selfhtml.org
User-Agent: Selflinkchecker 1.0 (http://aktuell.selfhtml.org/artikel/php/existenz/)
Connection: close


)

Wenn Sie nur testen wollen, ob die Resource existiert können Sie alles außer dem Schlüssel status ignorieren. Der Status 200 steht für OK, der Server kann die angeforderten Daten wie gewünscht versenden. Dies ist der Normalfall, wenn keine Probleme auftauchen.

Manchmal kann es sinnvoll sein, einen bestimmten Referer zu senden, wenn man die Resource abfragt (wenn der Server auf unterschiedliche Referer unterschiedlich reagiert):

$url = 'http://example.org/komische_unterseite/';
$result = http_test_existance($url, 10, 'Referer: '.$url);

Des Weiteren wäre es denkbar, einen Browser-Request so genau, wie möglich zu simulieren. Folgendes Beispiel führt einen Request durch und lässt den Server glauben, es handele sich um einen Mozilla Firefox in der Version 1.5 unter Windows 2000:

$result = http_test_existance('http://selfhtml.org/', 10,
 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.8) Gecko/20051107 Firefox/1.5',
 'Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
 'Accept-Language: de,en;q=0.7,en-us;q=0.3',
 'Accept-Encoding: gzip,deflate',
 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'
);

Beachten Sie:

Die Funktion kann mit Benutzernamen / Passwort-Angaben umgehen, wenn diese folgendermaßen angegeben werden: http://benutzer:passwort@example.org/. Diese werden in einen standardkonformen HTTP-Header für HTTP-Basic-Auth umgewandelt.

nach obennach unten

Weiterführende Links

Die folgenden Stellen werden empfohlen, um das obige Beispiel besser zu verstehen, oder um weitere Möglichkeiten und Details zu erfahren.

bereichsübergreifende Seite SELFHTML: Webserver/CGI: Webserver: HTTP-Status-Codes

englischsprachige Seite RFC 2616 - Hypertext Transfer Protocol - HTTP/1.1
englischsprachige Seite RFC 2617 - HTTP Authentication: Basic and Digest Access Authentication

deutschsprachige Seite PHP-Manual: parse_url
deutschsprachige Seite PHP-Manual: fsockopen
deutschsprachige Seite PHP-Manual: socket_set_timeout
deutschsprachige Seite PHP-Manual: base64_encode
deutschsprachige Seite PHP-Manual: isset
deutschsprachige Seite PHP-Manual: strpos
deutschsprachige Seite PHP-Manual: fputs
deutschsprachige Seite PHP-Manual: socket_get_status
deutschsprachige Seite PHP-Manual: fgets
deutschsprachige Seite PHP-Manual: fclose
deutschsprachige Seite PHP-Manual: str_replace
deutschsprachige Seite PHP-Manual: explode
deutschsprachige Seite PHP-Manual: array_shift
deutschsprachige Seite PHP-Manual: trim
deutschsprachige Seite PHP-Manual: strtolower
deutschsprachige Seite PHP-Manual: fopen

Teil von SELFHTML aktuell Teil von Artikel Teil von PHP

© 2007 bereichsübergreifende Seite Impressum