在百度貼吧(或 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" alt=""> </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" alt=""> </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 頭信息,確定圖片的類型,再把文件寫入相應類型的文件中。