跨域請求的幾種實現方式
跨域是瀏覽器的一種安全策略,是瀏覽器自身做的限制,不允許使用者訪問不同域名或埠或協議的網站資料。
只有域名(主域名【一級域名】和二級域名)、埠號、協議 完全相同的時候,才允許通訊。
通過前端實現跨域解決方案有:document.domain + iframe, window+name, HTML5 postMessage 。。。。詳見該篇文章
還有這篇
本文講述幾種通過後端實現跨域的解決方案:
1.jsonp
最常見的一種跨域方式,其背後原理就是利用了script標籤不受同源策略的限制,在頁面中動態插入了script,script標籤的src屬性就是後端api介面的地址,並且以get的方式將前端回撥處理函式名稱告訴後端,後端在響應請求時會將回調返還,並且將資料以引數的形式傳遞回去。
前端js:
//1.jsonp 跨域(涉及到前後端) 原理是利用script標籤不受同源策略的限制 在頁面中動態插入script標籤 標籤的src即為請求url的地址
//並將前端獲取到請求資料之後的回撥函式作為引數附加在url地址後
var script = document.createElement('script');
//注意 會有使用者名稱密碼 驗證的問題
script.src = "http://127.0.0.1:6666/tools/jsonpHandler?callback=_callback&username=admin&password=123456";
document.body.appendChild(script);
//回撥處理函式
var _callback = function(obj){
for(key in obj){
console.log("JSONP跨域測試 key: " + key + " value: " + obj[key]);
}
}
後端java:
@RequestMapping(value = "jsonpHandler")
public ModelAndView jsonpHandler(HttpServletRequest request, HttpServletResponse response) throws Exception{
String callback = request.getParameter("callback");
logger.info("callback: " + callback);
JSONObject obj = new JSONObject();
obj.put("type", "jsonp");
obj.put("method", "jsonHandler");
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write(callback + "(" + JSON.toJSONString(obj) + ")");
return null;
}
2.CORS
Cross-Origin Resource Sharing(跨域資源共享)是一種允許當前域(origin)的資源(比如html/js/web service)被其他域(origin)的指令碼請求訪問的機制。
當使用XMLHttpRequest傳送請求時,瀏覽器如果發現違反了同源策略就會自動加上一個請求頭:origin,後端在接受到請求後確定響應後會在Response Headers中加入一個屬性:Access-Control-Allow-Origin,值就是發起請求的源地址(http://127.0.0.1:8888),瀏覽器得到響應會進行判斷Access-Control-Allow-Origin的值是否和當前的地址相同,只有匹配成功後才進行響應處理。
現代瀏覽器中和移動端都支援CORS(除了opera mini),IE下需要8+
前端js:
//2.CORS 涉及到前後端
$.ajax({
type:"POST",
datatype:"JSON",
data:{
"param1":"param1-value",
"param2":"param2-value",
"username":"admin",
"password":"123456"
},
url:"http://127.0.0.1:6666/tools/corsHandler",
error:function(){
alert("請求失敗!");
},
success:function(data){
console.log("CORS跨域測試 data:");
console.log(data);
}
});
後臺java:
@RequestMapping(value = "corsHandler")
public ModelAndView corsHandler(HttpServletRequest request,HttpServletResponse response) throws IOException{
String param1 = request.getParameter("param1");
String param2 = request.getParameter("param2");
String requestUri = request.getRequestURI();
String requestUrl = request.getRequestURL().toString();//本域名下請求地址 不是客戶端的地址
Integer tempIndex = requestUrl.indexOf(requestUri);
//發出請求的源域名地址
String requestOrigin = requestUrl.substring(0, tempIndex);
if(request.getHeader("Origin") != null ){//跨域請求
requestOrigin = request.getHeader("Origin");
}
logger.info("requestUri: " + requestUri + "; requestUrl: " + requestUrl+ "; requestOrigin: " + requestOrigin);
JSONObject obj = new JSONObject();
obj.put("param1", param1);
obj.put("param2", param2);
//此方法allowOrigin中只能寫一個域名 因此改為與list對比的方式實現
/*String allowOrigin = appConfig.getAllowOrigin();
logger.info("allowOrigin: " + allowOrigin);
response.setHeader("Access-Control-Allow-Origin", allowOrigin);*/
response.setContentType("text/html; charset=UTF-8");
if(Constants.allowOriginList.
contains(requestOrigin)){
response.setHeader("Access-Control-Allow-Origin", requestOrigin);
}
response.getWriter().write( JSON.toJSONString(obj));
return null;
}
3.nginx反向代理
此方法最為便利,前後端程式碼無需做特殊改變,只需在nginx.conf做配置,將本地請求地址轉向真正實現請求的url地址。
js程式碼:
//3.用nginx代理解決跨域問題
$.ajax({
type:"POST",
datatype:"JSON",
data:{
"param1":"nginx-param1-value",
"param2":"nginx-param2-value",
"username":"admin",
"password":"123456"
},
url:"/apis/tools/nginxCrossOrigin",
error:function(){
alert("請求失敗!");
},
success:function(data){
console.log("nginxCrossOrigin 跨域測試 data:");
console.log(data);
}
});
java程式碼:
public ModelAndView nginxCrossOrigin(HttpServletRequest request,HttpServletResponse response) throws IOException{
String param1 = request.getParameter("param1");
String param2 = request.getParameter("param2");
logger.info("param1: " + param1 + "; param2 : " + param2);
JSONObject obj = new JSONObject();
obj.put("param1", param1);
obj.put("param2", param2);
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write( JSON.toJSONString(obj));
return null;
}
nginx.conf:
#for cross origin request
server {
#在瀏覽器中訪問源域名地址時 使用埠2222
listen 2222;
#源域名地址下訪問/apis/***開頭的url地址 會自動將請求轉向http://127.0.0.1:14444/***
location /apis {
#$1 第一個()中的值
rewrite ^.+apis/?(.*)$ /$1 break;
include uwsgi_params;
proxy_pass http://127.0.0.1:14444;
}
#發出請求的源域名地址
location / {
proxy_pass http://127.0.0.1:12222;
}
}
關於nginx和uwsgi的關係,可參考文章。
(1 )首先nginx 是對外的服務介面,外部瀏覽器通過url訪問nginx,
(2)nginx 接收到瀏覽器傳送過來的http請求,將包進行解析,分析url,如果是靜態檔案請求就直接訪問使用者給nginx配置的靜態檔案目錄,直接返回使用者請求的靜態檔案,
如果不是靜態檔案,而是一個動態的請求,那麼nginx就將請求轉發給uwsgi,uwsgi 接收到請求之後將包進行處理,處理成wsgi可以接受的格式,併發給wsgi,wsgi 根據請求呼叫應用程式的某個檔案,某個檔案的某個函式,最後處理完將返回值再次交給wsgi,wsgi將返回值進行打包,打包成uwsgi能夠接收的格式,uwsgi接收wsgi 傳送的請求,並轉發給nginx,nginx最終將返回值返回給瀏覽器。
(3)要知道第一級的nginx並不是必須的,uwsgi完全可以完成整個的和瀏覽器互動的流程,但是要考慮到某些情況
a.安全問題,程式不能直接被瀏覽器訪問到,而是通過nginx,nginx只開放某個介面,uwsgi本身是內網介面,這樣運維人員在nginx上加上安全性的限制,可以達到保護程式的作用。
b.負載均衡問題,一個uwsgi很可能不夠用,即使開了多個work也是不行,畢竟一臺機器的cpu和記憶體都是有限的,有了nginx做代理,一個nginx可以代理多臺uwsgi完成uwsgi的負載均衡。
c.靜態檔案問題,用django或是uwsgi這種東西來負責靜態檔案的處理是很浪費的行為,而且他們本身對檔案的處理也不如nginx好,所以整個靜態檔案的處理都直接由nginx完成,靜態檔案的訪問完全不去經過uwsgi以及其後面的東西。
4.後臺跨域
前端向本地後臺發出請求,後臺再向api伺服器請求資料,然後再將請求到的資料返回給前端。
後臺請求函式實現:
/**
* @param url 請求地址
* @param map 請求引數
* @param charset 字符集
*
* @return map - status 返回狀態 1-失敗 0-成功 Integer
* @return map - result 失敗原因 or 請求成功後api的返回資訊 String
*
* */
@SuppressWarnings({ "resource", "deprecation" })
public static HashMap<String,Object> doPost(String url,Map<String,Object> map,String charset){
HttpClient httpClient = null;
HttpPost httpPost = null;
String result = "伺服器出錯啦!請稍後重試!";
Integer status = 1;
HashMap<String,Object> returnMap = new HashMap<String,Object>();
try{
httpClient = new DefaultHttpClient();
httpPost = new HttpPost(url);
//設定引數
/* List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator<Entry<String, String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Entry<String,String> elem = (Entry<String, String>) iterator.next();
list.add(new BasicNameValuePair(elem.getKey(),elem.getValue()));
}
if(list.size() > 0){
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list,charset);
httpPost.setEntity(entity);
} */
String jsonStr = Json.toJson(map);
// String jsonStr = JSON.toJSONString(map);
logger.info("jsonStr: " + jsonStr);
logger.info("jsonStr2:" + JSON.toJSONString(map));
StringEntity entity = new StringEntity(jsonStr, "UTF-8");
entity.setContentType("application/json;charset=UTF-8");
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
if(HttpStatus.SC_OK == response.getStatusLine().getStatusCode()){
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
status = 0;
result = EntityUtils.toString(resEntity,charset);
}
}else{
status = 1;
result = "伺服器內部錯誤導致請求出錯!";
}
}else{
status = 1;
result = "伺服器內部錯誤導致請求出錯!";
}
}catch(Exception ex){
ex.printStackTrace();
status = 1;
result = "後臺出現異常,exception: " + ex.toString();
}
returnMap.put("status", status);
returnMap.put("result", result);
return returnMap;
}