在百度贴吧(或 QQ 空间等)中找到一张图片,复制图片地址,在站外通过 img src 引用,会发现:
此外,在一些统计软件中,统计访客的来路(直接访问、外部链接、搜索引擎),都用到了 HTTP 协议请求头中 Referer 的知识。
【例】直接访问 www.baidu.com 和 在通过本地页面跳转至 www.baidu.com,观察 HTTP 请求头信息的差异:
① 直接访问百度,HTTP 请求头信息:
② 通过本地 referer.html 跳转至 www.baidu.com:
HTTP 协议头信息的 Referer 选项代表网页的来源(上一页的地址),如果是直接访问(直接在浏览器输入地址访问),则没有 Referer 选项。
配置 Apache 服务器用于图片防盗链
原理:在 web 服务器层面,根据 http 协议的 referer 头信息来判断网页来源,如果来自站外,则统一重写到一个很小的防盗链题型图片上(URL 重写)。
步骤:
① 打开 apache 重写模块 rewrite_mod。重启 apache。
② 在需要防盗链的网站或目录下,写 .htaccess 文件,并指定防盗链规则 —— 分析 referer,如果不是来自本站,则重写
例如在 127.0.0.17/php/http/ 目录下新建 .htaccess 文件,写入重写规则:
当是 jpg/jpeg/gif/png 图片 ,且 referer 头与 127.0.0.17 不匹配时重写,统一 Rewrite 到某个防盗链图片
重写方式参见:apache 手册
D:\practise\php\http\.htaccess:
RewriteEngine On RewriteCond %{REQUEST_FILENAME} .*\.(jpg|jpeg|gif|png) [NC] RewriteCond %{HTTP_REFERER} !127.0.0.17 [NC] RewriteRule .* http://127.0.0.17/php/logo.jpg
第 1 行:开启重写功能;
第 2 行:当请求的文件名以 .jpg,.jpeg,.gif,.png 结尾时
第 3 行:当 HTTP 的 Referer 头 与 服务器地址 127.0.0.17 不匹配时
第 4 行:重写规则,把符合条件的文件重写到 http://127.0.0.17/php/http/logo.jpg 上
实例:
D:\practise\php\logo.jpg:
D:\practise\php\http\a.jpg:
站内地址(127.0.0.17,D:\practise),站外地址(127.0.0.16,D:\practise\php)。也就是说,127.0.0.16 上的文件外链 127.0.0.17 上的 a.jpg 时,会被重写至 logo.jpg。
http://127.0.0.17/php/http/referer.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <img src="./a.jpg" > </body> </html>
输出:
http://127.0.0.16/http/referer.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <img src="./http/a.jpg" > </body> </html>
输出:
防盗链功能实现。
如果把 .htaccess 放在根目录,则网站所有的图片都会受到影响;如果只要对某个目录生效,则在 .htaccess 中加上:
Rewrite Base /php/http
采集图片功能(通过设置 Referer 头信息,绕过防盗链)
先测试通过 HTTP GET 请求图片
caiji.php:
<?php require ‘./http.class.php‘; $http = new Http(‘http://127.0.0.17/php/http/a.jpg‘); echo $res = $http->get();
输出:
修改 http.class.php line:72(增加判断 $url[‘query‘],防止出现 Notice 级别的错误):
1 <?php 2 /* 3 PHP + socket 编程 4 @发送 HTTP 请求 5 @模拟下载 6 @实现注册、登录、批量发帖 7 */ 8 9 //http 请求类的接口 10 interface Proto{ 11 //连接 url 12 function conn($url); 13 14 //发送 GET 请求 15 function get(); 16 17 //发送 POST 请求 18 function post(); 19 20 //关闭连接 21 function close(); 22 } 23 24 class Http implements Proto{ 25 26 //换行符 27 const CRLF = "\r\n"; 28 29 //fsocket 的错误号与错误描述 30 protected $errno = -1; 31 protected $errstr = ‘‘; 32 33 //响应内容 34 protected $response = ‘‘; 35 36 protected $url = null; 37 protected $version = ‘HTTP/1.1‘; 38 protected $fh = null; 39 40 protected $line = array(); 41 protected $header = array(); 42 protected $body = array(); 43 44 public function __construct($url){ 45 $this->conn($url); 46 $this->setHeader(‘Host:‘ . $this->url[‘host‘]); 47 } 48 49 //写请求行 50 protected function setLine($method){ 51 $this->line[0] = $method . ‘ ‘ . $this->url[‘path‘] . ‘?‘ . $this->url[‘query‘] . ‘ ‘ . $this->version; 52 } 53 54 //写头信息 55 public function setHeader($headerline){ 56 $this->header[] = $headerline; 57 } 58 59 //写主体信息 60 protected function setBody($body){ 61 //构造 body 的字符串 62 $this->body[] = http_build_query($body); 63 } 64 65 //连接 url 66 public function conn($url){ 67 $this->url = parse_url($url); 68 //判断端口 69 if(!isset($this->url[‘port‘])){ 70 $this->url[‘port‘] = 80; 71 } 72 //判断 query 73 if(!isset($this->url[‘query‘])){ 74 $this->url[‘query‘] = ‘‘; 75 } 76 $this->fh = fsockopen($this->url[‘host‘], $this->url[‘port‘], $this->errno, $this->errstr, 3); 77 } 78 79 //构造 GET 请求的数据 80 public function get(){ 81 $this->setLine(‘GET‘); 82 //发送请求 83 $this->request(); 84 return $this->response; 85 } 86 87 //构造 POST 请求的数据 88 public function post($body = array()){ 89 //构造请求行 90 $this->setLine(‘POST‘); 91 92 //设置 Content-type 和 Content-length 93 $this->setHeader(‘Content-type: application/x-www-form-urlencoded‘); 94 95 //构造主体信息, 和 GET 请求不一样的地方 96 $this->setBody($body); 97 98 $this->setHeader(‘Content-length: ‘ . strlen($this->body[0])); 99 100 //发送请求 101 $this->request(); 102 return $this->response; 103 } 104 105 //发送请求 106 public function request(){ 107 //把请求行、头信息、主体信息拼接起来 108 $req = array_merge($this->line, $this->header, array(‘‘), $this->body, array(‘‘)); 109 $req = implode(self::CRLF, $req); 110 //echo $req;exit; 111 112 fwrite($this->fh, $req); 113 114 while(!feof($this->fh)){ 115 $this->response .= fread($this->fh, 1024); 116 } 117 118 //关闭连接 119 $this->close(); 120 } 121 122 //关闭连接 123 public function close(){ 124 fclose($this->fh); 125 } 126 }
测试防盗链:
caiji.php
1 <?php 2 require ‘./http.class.php‘; 3 4 $http = new Http(‘http://127.0.0.17/php/http/a.jpg‘); 5 6 $res = $http->get(); 7 8 //取出 图片二进制码(和 HTTP 头信息中间有一个空行 \r\n,加上空行之前的换行\r\n,一共4个字节) 9 $res = file_put_contents(‘./b.jpg‘, substr(strstr($res, "\r\n\r\n"), 4)); 10 echo ‘complete‘;
运行之后,因为没有 Referer 头信息,因此被认为是盗链,生成的 b.jpg 变成了 “不要盗链”:
在 caiji.php 中加入 Referer 头信息后:
caiji.php
1 <?php 2 require ‘./http.class.php‘; 3 4 $http = new Http(‘http://127.0.0.17/php/http/a.jpg‘); 5 $http->setHeader(‘Referer:127.0.0.17‘); 6 $res = $http->get(); 7 8 //取出 图片二进制码(和 HTTP 头信息中间有一个空行 \r\n,加上空行之前的换行\r\n,一共4个字节) 9 $res = file_put_contents(‘./b.jpg‘, substr(strstr($res, "\r\n\r\n"), 4)); 10 echo ‘complete‘;
此时采集到的 b.jpg 绕过了防盗链正常的显示了:
待完善:应该判断 response 的 MIME 头信息,确定图片的类型,再把文件写入相应类型的文件中。