出於安全方面的考慮,Web瀏覽器中JavaScript無法訪問其他服務器上的資源,這個限制僅在Web瀏覽器中有效。而跨域就是通過某些手段來繞過這個限制,實現不同服務器之間通信的效果。ajax跨域可以通過jsonp、cros或者服務端代理實現。
jsonp
jsonp利用<script>標籤不受限制訪問其他服務器資源的特點,通過<script>的src發送跨域訪問的url以及參數。jsonp需要對服務端返回的數據格式做修改,服務端返回的數據應該是一個可執行的JavaScript代碼。
如果json數據格式如下
{"sex": "男", "name": "小明", "age": 20}
那麼jsonp格式應該如下
callback({"sex": "男", "name": "小明", "age": 20});
其中callback是回調函數,一般由jsonp客戶端提供。jQuery提供了jsonp請求,下面用jsonp獲取京東的訂單列表數據。
$.ajax({
url: 'http://order.jd.com/lazy/getOrderListCountJson.action', // jsonp路徑
data: {}, // 參數
//jsonp: 'callback', // 服務端接收回調函數的參數,默認是callback
//jsonpCallback: '', // 回調函數名稱,如果不指定jQuery自動生成
dataType: 'jsonp' // 請求類型
}).done(function(json){
// 請求成功處理
console.dir(json);
}).fail(function(){
// 請求失敗處理
});
如圖所示,jsonp請求屬於script請求。
jsonp結果如下
cors
CORS(Cross-Origin Resource Sharing)定義一種跨域訪問的機制,可以讓AJAX實現跨域訪問。實現此功能非常簡單,只需服務器添加響應標頭Access-Control-Allow-Origin。目前大部分的PC瀏覽器和幾乎所有的移動瀏覽器都支持CROS。
瀏覽器 | CROS支持情況 |
---|---|
IE | 從IE8開始支持,其中IE8/IE9部分支持,IE10/IE11完全支持。 |
Firefox | Firefox 3.5+ 開始支持 |
Chrome | Chrome 3+ 開始支持 |
Opera | Opera 12+ 開始支持 |
Safair | Safair 4+ 開始支持 |
服務端代碼
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.fastjson.JSON;
@Controller
@RequestMapping("/test")
public class TestController {
/**
* 返回json
*
* @param response
*/
@RequestMapping("/json.do")
public void json(HttpServletResponse response) {
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
// 添加響應頭,支持跨域ajax請求
response.setHeader("Access-Control-Allow-Origin", "*");
// 構造json
Map<String, Object> json = new HashMap<String, Object>();
json.put("date", new Date());
json.put("name", "小明");
json.put("age", 18);
try {
// 這裏用FastJSON生成JSON字符串
response.getWriter().write(JSON.toJSONString(json));
} catch (IOException e) {
e.printStackTrace();
}
}
}
頁面代碼
$.ajax({
url: 'http://localhost:8280/logweb/test/json.do',
dataType: 'json',
success: function(json){
console.dir(json);
},
error: function(){
}
});
如圖所示,從CSDN頁面發起ajax跨域請求。
如果服務端沒有設置響應頭Access-Control-Allow-Origin,瀏覽器會有如下錯誤提示。
服務端代理
服務端不受跨域請求的限制,可以在服務端代理ajax請求。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
/**
* ajax請求代理
* 用Apache HttpClient模擬發送請求
* 所有請求都以POST方式提交
*/
@WebServlet("/ajax/proxy/servlet")
public class AjaxProxyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// ajax請求的實際url名稱
private final String destUrlParamName = "destUrl";
// 編碼
private final String encoding = "utf-8";
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 獲取請求參數
List<NameValuePair> params = getParameters(request);
// ajax請求的實際url
String destUrl = request.getParameter(destUrlParamName);
HttpPost httppost = new HttpPost(destUrl);
CloseableHttpClient httpclient = HttpClients.createDefault();
UrlEncodedFormEntity formEntity;
try{
// 設置請求參數
formEntity = new UrlEncodedFormEntity(params, encoding);
httppost.setEntity(formEntity);
CloseableHttpResponse httppostResponse = httpclient.execute(httppost);
try{
HttpEntity httpEntity = httppostResponse.getEntity();
InputStream httpis = httpEntity.getContent();
OutputStream os = response.getOutputStream();
int len = 0;
byte[] buffer = new byte[1024];
while((len = httpis.read(buffer)) != -1){
os.write(buffer, 0, len);
}
os.flush();
}finally{
httppostResponse.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 獲取ajax請求參數
* @param request
* @return 返回參數對數組
*/
private List<NameValuePair> getParameters(HttpServletRequest request){
Enumeration<String> paramNames = request.getParameterNames();
List<NameValuePair> params = new ArrayList<NameValuePair>();
while(paramNames.hasMoreElements()){
String name = paramNames.nextElement();
// 判斷,如果參數是ajax請求的目標路徑,則忽略
if(!destUrlParamName.equals(name)){
params.add(new BasicNameValuePair(name, request.getParameter(name)));
}
}
return params;
}
}
ajax跨域請求代碼
/**
* ajax代理請求
* @param options ajax配置參數,請參考$.ajax()的配置
*/
function ajaxProxy(options){
// ajax代理的url
var proxyUrl = 'http://localhost:8280/logweb/ajax/proxy/servlet',
destUrl = options.url;
// 把代理url和實際url替換
options.url = proxyUrl;
if(!options.data){
options.data = {};
}
// 把實際url放到參數destUrl中,傳遞給服務端發送代理請求
options.data.destUrl = destUrl;
options.type = 'post';
return $.ajax(options);
}
下面用ajax代理請求博客園的分頁數據
ajaxProxy({
url: 'http://www.cnblogs.com/mvc/AggSite/PostList.aspx', // 博客園分頁url
dataType: 'html', // 數據格式是html
data: {
CategoryId: 808,
CategoryType: "SiteHome",
ItemListActionName: "PostList",
PageIndex: 2,
ParentCategoryId: 0
},
success: function(html){
console.dir(html);
//把html直接輸出到頁面
$('body').html(html);
},
error: function(){
console.dir(arguments);
}
});
請求參數如圖所示
執行結果如圖所示
總結
優點 | 缺點 | |
---|---|---|
jsonp | 不存在兼容性問題,大部分js庫都支持jsonp。 | 通過script發送請求,url長度有限制,不能發送超過2048字節的請求。服務端需要提供專門的jsonp接口。 |
cros | Web端和服務端基本上不用做代碼修改。 | 不能完全兼容所有瀏覽器。 |
ajax代理 | 不存在兼容性問題 | 相對於jsonp和cros,性能可能較差。服務端需要提供專門的代理。 |