
Questa classe nasce dall’esigenza di revisionare i numerosi bookmarks accumulati in anni di navigazione su Internet. Purtroppo uno degli inconvenienti di Internet è proprio la scarsa affidabilità sulla persistenza dei link. Succede spesso che i link collezionati anni prima non siano più attivi oppure siano stati ridirezionati su altri siti.
Questa classe utilizza le librerie cURL che nelle ultime versioni del PHP, sono state integrate nel pacchetto. Ho utilizzato in particolare la famiglia di comandi curl_multi*
in modo da poter evadere richieste multiple parallelamente e velocizzare notevolmente il processo. Oltre a poter verificare le url, che vengono fornite al costruttore della classe come array, per particolari status code o per intere famiglie di status code, è possibile anche verificare che la risposta non sia un cosiddetto hit-nxdomain cioè un server che intercetta un nxdomain e propone un redirect ad una pagina di ricerca di domini dal nome simile. In pratica alcuni DNS (p.e. OpenDNS) in caso di dominio inesistente producono redirect pubblicitari attraverso i loro hit-nxdomain.
Le proprietà pubbliche della classe sono:
Nome | Descrizione |
---|---|
$errors | Array che contiene la lista di url che hanno determinato un errore cURL |
$maxUrls | Il numero massimo di url da processare parallelamente |
$statusCodeType | Stringa filtro per gli status code e le famiglie di status code (p.e. 3xx) Possono essere abbinate e devono essere separate da , p.e. 3xx,404,403. |
$timeout | Timeout in secondi per l’attesa della risposta del server |
$ua | Stringa che identifica lo user agent con la quale viene formalizzata la richiesta cURL |
Per la gestione delle risposte multiple alle chiamate cURL HTTP, ho abbondantemente utilizzato le ottime risorse del manuale PHP….!
Ed ecco il codice della classe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | <?php /** * class: CheckUrls * Check status code in a list of Urls with Curl Multi thread library * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * @author Mario Spada <spadamar@spadamar.com> * @copyright Copyright (c) 2013 Mario Spada * @package CheckUrls */ class CheckUrls { /** * timeout (default = 30s) * @var int */ public $timeout = 30; /** * Max number of urls to check (default = 50) * @var int */ public $maxUrls = 50; /** * User Agent string (default = Chrome 27 on Windows XP ) * @var string */ public $ua = "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36"; /** * Array filled with Curl scan errors * @var array */ public $errors = array(); /** * String filtering status code, If more than one, separate it with comma. * Type a group e.g. 4xx and/or a single code e.g. 404. * Example "3xx,404" means filter all 3xx and 404. * Default: "1xx,2xx,3xx,4xx,5xx" (all) * * @var string */ public $statusCodeType = "1xx,2xx,3xx,4xx,5xx"; private $urls2check = array(); private $nUrls = 0; private $ch = NULL; private $mh = NULL; private $still_running = NULL; private $hitNxdomainList = array("67.215.65.132"); /** * @param array $urlList (list of Urls to check) */ public function __construct($urlList) { if (count($urlList) < 1) { die ("Empty urls list!"); } if (!function_exists("curl_multi_init")) { die ("Sorry, cURL Libraries aren't available"); } $this->urls2check = (array) $urlList; $this->nUrls = count($this->urls2check); if ($this->nUrls > $this->maxUrls) { die ("Sorry, the number of urls to check exceed MAX: ".$this->maxUrls); } } private function initCurl () { for ($n = 1; $n <= $this->nUrls; $n++){ $this->ch[$n] = curl_init(); } } private function curlSetOptions () { $n = 1; foreach ($this->urls2check as $value) { curl_setopt($this->ch[$n], CURLOPT_URL, $value); curl_setopt($this->ch[$n], CURLOPT_NOBODY, true); curl_setopt($this->ch[$n], CURLOPT_HEADER, true); curl_setopt($this->ch[$n], CURLOPT_USERAGENT, $this->ua); curl_setopt($this->ch[$n], CURLOPT_RETURNTRANSFER, true); curl_setopt($this->ch[$n], CURLOPT_TIMEOUT, $this->timeout); $n++; } } private function addCurlHandles () { $this->mh = curl_multi_init(); for ($n = 1; $n <= $this->nUrls; $n++){ curl_multi_add_handle($this->mh,$this->ch[$n]); } } /** * Parse urls and return an array of status code. * @return array */ public function parse() { $statusCodePattern = $this->composeStatusCodePattern(); $res = array(); $this->errors = array(); $this->initCurl(); $this->curlSetOptions(); $this->addCurlHandles(); $this->still_running = NULL; $this->full_curl_multi_exec(); // start requests do { // "wait for completion"-loop curl_multi_select($this->mh); // non-busy (!) wait for state change $this->full_curl_multi_exec(); // get new state while ($info = curl_multi_info_read($this->mh)) { $headers = curl_multi_getcontent($info['handle']); $url = curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL); $status = curl_getinfo($info['handle'],CURLINFO_HTTP_CODE); $redir_url = "NA"; // is_NXDOMAIN ? $isnxdomain = $this->is_nxdomain($url); if ( $info['result'] == CURLE_OK ) { if (preg_match($statusCodePattern,$status)) { if($status === 301 || $status === 302) { if (preg_match("!\r\n(?:Location|URI): *(.*?) *\r\n!", $headers, $matches)) { $redir_url = $matches[1]; } } } } else { array_push($this->errors, $url); } array_push($res,array('url'=>$url, 'status'=>$status, 'redirect'=>$redir_url, 'nxdomain'=>$isnxdomain)); } } while ($this->still_running); $this->closeAll(); return $res; } private function full_curl_multi_exec() { do { $rv = curl_multi_exec($this->mh, $this->still_running); } while ($rv == CURLM_CALL_MULTI_PERFORM); return $rv; } private function closeAll () { for ($n = 1; $n <=$this->nUrls; $n++){ curl_multi_remove_handle($this->mh, $this->ch[$n]); } curl_multi_close($this->mh); } private function composeStatusCodePattern () { $res = ""; $this->statusCodeType = preg_replace('/\s+/', '',$this->statusCodeType); $codesFilter = explode(",",$this->statusCodeType); foreach ($codesFilter as $code) { if (is_numeric($code)) { $res .= $code."|"; } else { $res .= str_replace("x","\d",$code)."|"; } } $res = substr($res, 0, -1); $res = "/(".$res.")/"; return $res; } private function is_nxdomain ($url) { $hostfromurl = parse_url($url, PHP_URL_HOST); $res = gethostbyname($hostfromurl); if ($res == $hostfromurl || in_array($res,$this->hitNxdomainList)) { return true; } return false; } } ?> |
E questo è un esempio di utilizzo della classe:
<?php require_once("CheckUrls.class.php"); $urls = array("www.google.com", "www.facebook.com", "www.questaeunaprova.it"); $check = new CheckUrls($urls); $result = $check->parse(); echo "<p>Risultati</p>\n"; print_r($result); echo "<p>Errori</p>\n"; print_r($check->errors); ?> |
Questo è l’output:
Risultati Array ( [0] => Array ( [url] => HTTP://www.questaeunaprova.it [status] => 0 [redirect] => NA [nxdomain] => 1 ) [1] => Array ( [url] => HTTP://www.google.com [status] => 302 [redirect] => http://www.google.it/ [nxdomain] => ) [2] => Array ( [url] => HTTP://www.facebook.com [status] => 301 [redirect] => https://www.facebook.com/ [nxdomain] => ) ) Errori Array ( [0] => HTTP://www.questaeunaprova.it )
Ho preparato anche la documentazione del codice generata con phpSimpleDoc
Al momento ho potuto constatare che solo OpenDNS utilizza un redirect per i nxdomanin, e ho inserito nella proprietà: $hitNxdomainList
l’indirizzo IP del suo hit-nxdomain. Non ho utilizzato un metodo per gestire il contenuto di questo array, perché ritengo che sarà utilizzato poco. Ovviamente, se fosse necessario aggiungerne altri, è possibile modificare direttamente il codice alla riga: 60.
Il pacchetto con la classe e il file di esempio è disponibile qui.