![]() |
PHP:
|
|
| |
| E-Mail: | |
|---|---|
| Homepage-URL: |
Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!
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.
fopen()
function http_test_existance($url) {
return (($fp = @fopen($url, 'r')) === false) ? false : @fclose($fp);
}
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:
301 würde sich hinter der Resource beispielsweise eine Weiterleitung verbergen. PHP folgt einfach dieser Weiterleitung und die Funktion würde true zurückgeben - ohne, dass man Einfluss darauf hätte. Ferner wird bei Fehlern nicht weiter unterschieden, man hat also keine Möglichkeit, bei einen temporären Serverausfall anders zu reagieren, als bei einem 404 Not Found.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.';
}
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;
}
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:
array('status' => -1).array('status' => -2).array('status' => -3). Der Pfad muss mit einem Slash beginnen, http://example.com ist also im Gegensatz zu http://example.com/ falsch.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:
User-Agent: Selflinkchecker 1.0 (http://aktuell.de.selfhtml.org/artikel/php/existenz/) gesendet.null (oder ein Leerstring) als dritter Parameter angegeben, so wird kein User-Agent gesendet.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.
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'
);
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.
Die folgenden Stellen werden empfohlen, um das obige Beispiel besser zu verstehen, oder um weitere Möglichkeiten und Details zu erfahren.
SELFHTML: Webserver/CGI: Webserver: HTTP-Status-Codes
RFC 2616 - Hypertext Transfer Protocol - HTTP/1.1
RFC 2617 - HTTP Authentication: Basic and Digest Access Authentication
PHP-Manual: parse_url
PHP-Manual: fsockopen
PHP-Manual: socket_set_timeout
PHP-Manual: base64_encode
PHP-Manual: isset
PHP-Manual: strpos
PHP-Manual: fputs
PHP-Manual: socket_get_status
PHP-Manual: fgets
PHP-Manual: fclose
PHP-Manual: str_replace
PHP-Manual: explode
PHP-Manual: array_shift
PHP-Manual: trim
PHP-Manual: strtolower
PHP-Manual: fopen