業務場景
1、頁面引用其他站點圖片的時候,由於某些站點存在圖片的防盜鏈機制,所以在引用圖片的時候,返回的一張默認的圖片,而不是原圖片。
2、使用java完成一個代理程序,代理所有的存在防盜鏈機制的圖片請求,繞過防盜鏈機制,返回原圖片
解決思路
1、代理請求傳輸的http頭信息Host,使用圖片url的Host,而自身站點的Host地址
2、代理請求傳輸的http頭信息Referer,使用圖片url的Host,前面加上”http://”
3、完成以上1、2的操作,用以模擬圖片本身站點對圖片的請求,繞過反防盜鏈機制
代理相關的工具類
當中使用了Apache Commons IO,解決的網絡圖片獲取不完整的bug(需要的包請點擊下載)
package lib;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import com.mysql.jdbc.StringUtils;
import dto.ImageData;
/**
* 用於獲取url的圖片數據
* @author yuyu
*
*/
public class GetProxyImage {
/**
* 根據傳入的url將結果以byte[]的數據返回
* @param url 需要請求的url
* @return
* @throws Exception
*/
public static ImageData getImage(String url)throws Exception{
//當傳入的url返回不爲空的時候,讀取數據
ImageData back=null;
InputStream input;
if(!StringUtils.isNullOrEmpty(url)){
try{
back =new ImageData();
String ContentType=null;
//設置請求的頭信息
URL urlInfo = new URL(url);
URLConnection connection = urlInfo.openConnection();
//設置頭信息
connection.addRequestProperty("Host", urlInfo.getHost());
connection.addRequestProperty("Connection", "keep-alive");
//強制要求緩存服務器在返回緩存的版本之前將請求提交到源頭服務器進行驗證。
connection.addRequestProperty("Cache-Control", "no-cache");
//使用url的Host來標記來源
connection.addRequestProperty("Referer", "http://"+urlInfo.getHost());
//表示用戶不願意目標站點追蹤用戶個人信息。
connection.addRequestProperty("DNT", "1");
//強制要求緩存服務器在返回緩存的版本之前將請求提交到源頭服務器進行驗證。
connection.addRequestProperty("Pragma", "no-cache");
connection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36");
connection.connect();
//獲取頭信息,圖片的返回格式
Map<String, List<String>> map = connection.getHeaderFields();
List<String> type=map.get("Content-Type");
ContentType=type.get(0);
//獲取請求回來的信息
input = connection.getInputStream();
//解決使用inputStream.available(),讀取圖片不完整問題
//此方法在內部緩衝輸入,因此不需要使用BufferedInputStream。
byte[]data = IOUtils.toByteArray(input);
input.close();
//設置返回信息
back.setContentType(ContentType);
back.setData(data);
}catch(Exception e){
throw new Exception("讀取Url數據失敗:"+url,e);
}
}
return back;
}
}
用以傳輸圖片對象的簡單類
package dto;
/**
* 用於http圖片數據請求
* @author yuyu
*
*/
public class ImageData {
//圖片返回的格式信息
private String ContentType;
//圖片返回數據的字節數組
private byte[] data;
public String getContentType() {
return ContentType;
}
public void setContentType(String contentType) {
ContentType = contentType;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
@Override
public String toString() {
return "ImageData [ContentType=" + ContentType + ", data長度="
+ data.length + "]";
}
}
處理代理請求的Servlet
package proxyImage;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dto.ImageData;
import lib.GetProxyImage;
@SuppressWarnings("serial")
public class ProxyImage extends HttpServlet {
/**
* 接收一個代理圖片的url,返回代理得來的圖片數據
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String url=request.getParameter("url");
//獲取圖片信息
try {
ImageData image = GetProxyImage.getImage(url);
System.out.println(image);
if(image!=null){
//將獲取到的圖片返回
response.setContentType(image.getContentType());
//設置瀏覽器緩存
response.setHeader("Cache-Control", "max-age=31536000");
OutputStream out = response.getOutputStream();
out.write(image.getData());
out.flush();
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
展示jsp頁面
頁面的具體數據綁定使用了vue 框架(詳情請點擊查看)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>proxy image page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body style="text-align:center">
<div id="app" >
<h2>{{ message }}</h2>
<input v-model="imageUrl" style="width: 800px;height: 30px;" Placeholder="請輸入圖片地址">
<br>
<div style="display: inline-block;">
<h5>原圖片</h5>
<img :src="imageUrl" style="width: 400px;height: 300px;">
</div>
<div style="display: inline-block;">
<h5>代理圖片</h5>
<img :src="proxyImageUrl" style="width: 400px;height: 300px;">
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: '圖片代理展示',
imageUrl: ''
},
computed: {
proxyImageUrl: function () {
return 'servlet/ProxyImage?url='+encodeURIComponent(this.imageUrl)
}
}
})
</script>
</body>
</html>
測試
一、百度
1、從百度引用的圖片地址:
http://a.hiphotos.baidu.com/image/w%3D230/sign=fa52b971b1de9c82a665fe8c5c8180d2/d53f8794a4c27d1eba11668c19d5ad6eddc43846.jpg
2、代理截圖:
二、QQ空間
1、從QQ空間引用的圖片地址:
http://a4.qpic.cn/psb?/f2006c66-5773-4a59-8984-2cbea031e9c0/WB*zO7jU3cQhrGgLSKf9Vd5c.YNcq67BGOWQmdO1qkk!/m/dEMBAAAAAAAA
2、代理截圖:
總結
1、如果代理的圖片過大,或者過於頻繁其實可以將代理的結果緩存起來
2、這個代理程序只適合於其他站點的靜態圖片獲取,要是動態的隱私圖片可能會根據登入用戶的cookie或者ip顯示,這個時候就會代理失敗