在PHP中重定向之前检查链接有效性的最快方法

问题描述:

我一直在我的ErrorDocument页面上运行脚本,该脚本在引用者是我的站点时记录并通过电子邮件发送给我。最近我以为我可以使用相同的脚本来记录外部链接。我试图检索外部页面标题,如果它们显示一个404留在我的网站上的错误消息,如果没有,然后做重定向。这是可行的,但即使网站可用,速度也很慢。比直接访问外部页面慢得多。在PHP中重定向之前检查链接有效性的最快方法

我认为这个问题必须躺在我是如何检索头(见function is404),而不是在错误报告或全部preg_replace的,因为它的工作原理相当快,当我使用它在我的ErrorDocument的,但无论如何,我会发布所有代码以防其他地方出现问题。

题外话: 我还没有决定是否要部署还没有这个,很明显它是依赖于获取速度问题固定的,但我想知道你的意见是对这种方法假定它工作加快速度的。我的网站的好处是明确的,它使用户停留在网站上,而不是倾倒到可能无法解读的404上,并让我知道断开的链接。但是,如果外部页面具有自定义404,那么在断开链接后用户可能会更好。我试图通过在错误消息中包含指向404的链接并检查域的可用性并提供一个指向它的链接(如果可用)来抵消这一点。其他的东西也是可行的,例如链接到谷歌的结果。那么,如果你遇到了这个问题,你会感到高兴,矛盾,轻度惹恼,还是反感?

更新: 我在最后还是决定去一个框架式的解决方案,由梅尔,因此我在iframe加载外部页面,并在任何允许用户报告打破了顶部的小酒吧的建议链接应该存在。这个解决方案中存在的问题是,如果我实际上没有重定向页面,如果用户希望为其添加书签,他们最终会得到一个指向我的重定向脚本的链接(除非引用者是我的网站,否则它会被阻止;所以他们会链接到禁止页面)。这通过一个简单的window.location=http://external.site修复,但问题是何时调用它。 iframe onload事件触发页面是404还是没有,所以这是不好的,并使用一个简单的超时使页面显然已经加载只是看起来丑陋与我的“有这个页面错误”消息在顶部。我去了一个组合。当onload触发时,我正在对脚本进行Ajax调用,该脚本从我的服务器检查外部站点的状态。 (我使用的方法与我下面的方法相同,但是这次似乎运行的非常好,我认为速度问题在其他地方,尽管我不知道在哪里)。我认为使用Ajax调用(使用超时)将防止Adrian指出的站点阻塞问题。如果我的服务器认为该页面不是404,我立即删除横幅。如果它认为这是我设置了一个超时,并留下标志(希望)足够长的用户想要点击它。正如答案中指出的那样,有些情况下我的404代码与用户看到的不匹配,但是最糟糕的情况是错过了一个错误的机会来报告错误或在罚款期间停留一段时间的横幅页。希望大部分时间都能解决。对于没有启用JavaScript的用户,我只需使用meta refresh并立即转发到链接。如果我在这里出了问题或错过了其他重要的东西,我会很感激你的反馈。谢谢。


下面的代码:

// /../php/urlfns.php 

    1 <?php 
    2 function hasProtocol($uri) { 
    3  return preg_match('#^.+://#', $uri); 
    4 } 
    5 
    6 function getDomain($uri, $keepproto=false) { 
    7  return preg_replace('#^((.+://)?(.+?))\/.*#', $keepproto?'${1}':'${3}', $uri.'/'); 
    8 } 
    9 
10 function getBaseDomain($uri, $keepproto=false) { 
11  $dom = getDomain($uri, $keepproto); 
12  if(!$keepproto) { 
13   return preg_replace('#.*\.(.+\..+)$#', '${1}', '.'.$dom); 
14  } 
15  else 
16  if(hasProtocol($dom)) { 
17   return preg_replace('#^(.+://).*\.(.+\..+)$#', '${1}${2}', $dom); 
18  } 
19  else { 
20   return preg_replace('#^.*\.(.+\..+)$#', '${1}', '.'.$dom); 
21  } 
22 } 
23 
24 function isOnDomain($uri, $domain, $allowsubs=false) { 
25  $uridom = getDomain($uri); 
26 
27  if(!$allowsubs) { 
28   return ($uridom===$domain); 
29  } 
30  else { 
31   $basedom = getBaseDomain($domain); 
32   return (strlen($uridom) - strlen($basedom) === strrpos($uridom, $basedom)); 
33  } 
34 } 
35 
36 function stripDomain($uri) { 
37  return preg_replace('#^(.+://)?.*?(/.*)#', '${2}', $uri); 
38 } 
39 
40 function is404($uri) { 
41  stream_context_get_default(array(
42   'http'=>array(
43    'method' => 'HEAD' 
44  ) 
45 )); 
46  $hds = @get_headers($uri); 
47  return (!$hds || strpos($hds[0], ' 404 ') !== false); 
48 } 
49 ?> 

**

// /../php/badlink.php 

    1 <?php 
    2 require_once('urlfns.php'); 
    3 
    4 function reportbadlink($lntype='generic', $subdomains=false, $blcache='../badlinks/') { 
    5 
    6  if(isset($_SERVER['HTTP_REFERER']) && 
    7   isOnDomain($_SERVER['HTTP_REFERER'], $_SERVER['SERVER_NAME'], $subdomains)) { 
    8 
    9   $reffile = $_SERVER['DOCUMENT_ROOT'].'/'.stripDomain($_SERVER['HTTP_REFERER']); 
10 
11   $blid = md5($lntype.$_SERVER['REQUEST_URI'][email protected]($reffile)); 
12   $blfil = dirname(__FILE__).'/'.$blcache.'/'.$blid; 
13 
14   if(!file_exists($blfil)) { 
15    $report = ' On: '.$_SERVER['HTTP_REFERER']."\n". 
16      ' To: '.$_SERVER['REQUEST_URI']."\n". 
17      'Type: '.$lntype."\n". 
18      ' #: '.$blid."\n\n"; 
19 
20    file_put_contents($blfil, $report); 
21    mail('[email protected]', 'A broken link has been found.', $report); 
22   } 
23  } 
24 } 
25 ?> 

**

// /ssi/extlink.php 
    1 <? 
    2  require_once('../../php/urlfns.php'); 
    3  $uri = $_SERVER['QUERY_STRING']; 
    4 
    5  if(!hasProtocol($uri)) { 
    6   $uri = 'http://'.$uri; 
    7  } 
    8  
    9  if(!is404($uri)) { 
10   header('Location: '.$uri); 
11   exit; 
12  } 
13   
14  header("Cache-Control: no-cache, must-revalidate"); 
15  header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); 
16 
17  require_once('../../php/badlink.php'); 
18  reportbadlink('external'); 
19   
20  $baseuri = getDomain($uri,true); 
21  if(preg_match('#^'.$baseuri.'/?$#', $uri) || is404($baseuri)) { 
22   $baseuri = null; 
23  } 
24     
25  $errtype = 'External page not found'; 
26  $errmesg = '<p><em><a href="'.$uri.'">'.$uri.'</a> was not found.</em></p>'. 
27    '<p>This may be because the site is temporarily unavailable, or it may be a broken link.</p>'. 
28    ($baseuri ? '<p><a href="'.$baseuri.'">'.$baseuri.'</a> appears to be available however and you may find '. 
29    ' what you are looking for there.</p>' : ''); 
30 ?> 
31 <!DOCTYPE html> 
32 <html lang="en" dir="ltr"> 
    ... 

我绝对不会部署这个,只是因为你的假设是,如果你可以到达该网站,那么你的用户也可以。然而,如果你不能,但用户可以因为本地化的路由问题,你已经给他们发送了一条红色鲱鱼。

我宁愿部署一个框架解决方案,在小顶部框架中,您有一个按钮可以将链接报告为已损坏。与Google图片的右侧窗格类似。然后,您可以在链接报告的最后x天内报告链接多少次,并在链接旁边显示链接数量。

对我来说,最终用户会好得多,然后有人声称该网站不适合我。 “我不相信你,你在那里做什么阴暗的事情,我只是复制链接,粘贴到我的地址栏中,自己去那里!”。再一次,我是一个愤世嫉俗的人。

+0

谢谢,你说得很好。如果它对最终用户体验产生不利影响,那绝对是我不想做的事情。我和你一样,是一个愤世嫉俗的人,会和你一样做,我只是想知道我的大多数用户是否会一样。感谢您的输入。 – tjm 2011-05-16 13:31:10

+0

嗯。我也喜欢这个解决方案,并且想知道如果一次加载到框架中,我可以以某种方式访问​​响应头文件(也许通过javascript),从而获得两全其美的好处,但快速搜索似乎表明这是不可能的。 – tjm 2011-05-16 14:11:57

我会与外部网页的SQL表,并定期检查网页与cron任务。这样,人们点击几乎没有任何延迟。

+0

我曾希望避免使用SQL,但我可以将它看作一种可能的解决方案。谢谢。 – tjm 2011-05-16 13:32:22

您也可以使用fsockopen来打开URL。如果返回的资源有errno of 404那么它的一个断开的链接。保存在所有的正则表达式

+0

现在关闭阅读'fsockopen',谢谢。 – tjm 2011-05-16 13:32:42

管理员应该在外部链接断开时得到通知的想法非常整齐,但作为用户,我不想从您的网站获取该消息。我不会相信它是一个真正的404,所以我可能会复制该URL并将其粘贴到一个新的选项卡中,以便检查。这会让我做更多的工作,而不是帮助我。另外,正如梅尔所说,如果你的支票出错了怎么办?

如果您仍想检查外部链接的有效性,请确保它不会以任何方式影响用户。使用jodes提到的cron脚本似乎是一个好主意。

有点关系的故事,可能深思:

我已经使用了PHP脚本来检查图像中的另一个域的存在(我们的域名,但另一台服务器上...)和替代如果检查失败,图像href为默认图像href。这工作正常了一个星期左右(虽然它使网站有点慢),但星期一我们得到了另一个域(电子商务服务器)的巨大流量高峰,该域名下降,突然我们有加载时间在几分钟的网站,我是在工作。

换句话说,这是™

我懵服务器端检查出真快,为了再升获得网站非常糟糕的主意。后来,我构建了一个小型javascript,将附加事件侦听器添加到图像上,触发加载,并用默认图像替换返回404的任何图像。它工作的很好,对于少数没有启用js的用户来说,它可以工作,即使它在资源管理器中看起来有点丑陋(破碎的图标图标)。

+0

感谢您分享您的观点和经验。我正在考虑混合使用jodes和Mels响应的方式。 – tjm 2011-05-16 14:16:33