1. 程式人生 > >解決ajax跨域問題【5種解決方案】

解決ajax跨域問題【5種解決方案】

什麼是跨域問題?

跨域問題來源於JavaScript的"同源策略",即只有 協議+主機名+埠號 (如存在)相同,則允許相互訪問。也就是說JavaScript只能訪問和操作自己域下的資源,不能訪問和操作其他域下的資源。跨域問題是針對JS和ajax的,html本身沒有跨域問題。

檢視瀏覽器開發者工具Console報錯:

Failed to load http://a.a.com:8080/A/FromServlet?userName=123: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://b.b.com:8080' is therefore not allowed access.

http://www.abc.com/a/b 呼叫 http://www.abc.com/d/c(非跨域)

http://www.abc.com/a/b 呼叫 http://www.def.com/a/b (跨域:域名不一致)

http://www.abc.com:8080/a/b 呼叫 http://www.abc.com:8081/d/c (跨域:埠不一致)

http://www.abc.com/a/b 呼叫 https://www.abc.com/d/c (跨域:協議不同)

請注意:localhost和127.0.0.1雖然都指向本機,但也屬於跨域。

跨域問題怎麼解決?

1、響應頭新增Header允許訪問

2、jsonp 只支援get請求不支援post請求

3、httpClient內部轉發

4、使用介面閘道器——nginx、springcloud zuul   (網際網路公司常規解決方案)

解決方式1:響應頭新增Header允許訪問

跨域資源共享(CORS)Cross-Origin Resource Sharing

response.addHeader(‘Access-Control-Allow-Origin:*’);//允許所有來源訪問 
response.addHeader(‘Access-Control-Allow-Method:POST,GET’);//允許訪問的方式

解決方式2:jsonp 只支援get請求不支援post請求

用法:①dataType改為jsonp     ②jsonp : "jsonpCallback"————傳送到後端實際為jsonpCallback=jQueryxxx     ③後端獲取get請求中的jsonpCallback    ④構造回撥結構

$.ajax({
			type : "GET",
			async : false,
			url : "http://a.a.com/a/FromServlet?userName=644064",
			dataType : "jsonp",//資料型別為jsonp  
			jsonp : "jsonpCallback",//服務端用於接收callback呼叫的function名的引數
			success : function(data) {
				alert(data["userName"]);
			},
			error : function() {
				alert('fail');
			}
		});
//後端
        String jsonpCallback = request.getParameter("jsonpCallback");
		//構造回撥函式格式jsonpCallback(資料)
		resp.getWriter().println(jsonpCallback+"("+jsonObject.toJSONString()+")");

JSONP實現原理

在同源策略下,在某個伺服器下的頁面是無法獲取到該伺服器以外的資料的,即一般的ajax是不能進行跨域請求的。但 img、iframe 、script等標籤是個例外,這些標籤可以通過src屬性請求到其他伺服器上的資料。利用<script>標籤的開放策略,我們可以實現跨域請求資料,當然這需要伺服器端的配合。 Jquery中ajax的核心是通過 XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態新增<script>標籤來呼叫伺服器提供的 js指令碼。

  當我們正常地請求一個JSON資料的時候,服務端返回的是一串JSON型別的資料,而我們使用 JSONP模式來請求資料的時候服務端返回的是一段可執行的JavaScript程式碼。因為jsonp 跨域的原理就是用的動態載入<script>的src ,所以我們只能把引數通過url的方式傳遞,所以jsonp的 type型別只能是get !

示例:

$.ajax({

    url: 'http://192.168.10.46/demo/test.jsp',        //不同的域

    type: 'GET',                                                        // jsonp模式只有GET 是合法的

    data: {

        'action': 'aaron'

    },

    dataType: 'jsonp',                                              // 資料型別

    jsonp: 'jsonpCallback',                                     // 指定回撥函式名,與伺服器端接收的一致,並回傳回來

})

其實jquery 內部會轉化成

http://192.168.10.46/demo/test.jsp?jsonpCallback=jQuery202003573935762227615_1402643146875&action=aaron

然後動態載入

<script type="text/javascript"src="http://192.168.10.46/demo/test.jsp?jsonpCallback= jQuery202003573935762227615_1402643146875&action=aaron"></script>

然後後端就會執行jsonpCallback(傳遞引數 ),把資料通過實參的形式傳送出去。

  使用JSONP 模式來請求資料的整個流程:客戶端傳送一個請求,規定一個可執行的函式名(這裡就是 jQuery做了封裝的處理,自動幫你生成回撥函式並把資料取出來供success屬性方法來呼叫,而不是傳遞的一個回撥控制代碼),伺服器端接受了這個 jsonpCallback函式名,然後把資料通過實參的形式傳送出去

(在jquery 原始碼中, jsonp的實現方式是動態新增<script>標籤來呼叫伺服器提供的 js指令碼。jquery 會在window物件中載入一個全域性的函式,當 <script>程式碼插入時函式執行,執行完畢後就 <script>會被移除。同時jquery還對非跨域的請求進行了優化,如果這個請求是在同一個域名下那麼他就會像正常的 Ajax請求一樣工作。)

解決方式3:httpClient內部轉發

實現原理很簡單,若想在B站點中通過Ajax訪問A站點獲取結果,固然有ajax跨域問題,但在B站點中訪問B站點獲取結果,不存在跨域問題,這種方式實際上是在B站點中ajax請求訪問B站點的HttpClient,再通過HttpClient轉發請求獲取A站點的資料結果。但這種方式產生了兩次請求,效率低,但內部請求,抓包工具無法分析,安全。

$.ajax({
			type : "GET",
			async : false,
			url : "http://b.b.com:8080/B/FromAjaxservlet?userName=644064",
			dataType : "json",
			success : function(data) {
				alert(data["userName"]);
			},
			error : function() {
				alert('fail');
			}
		});
@WebServlet("/FromAjaxservlet")
public class FromAjaxservlet extends HttpServlet{
	
	
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//建立預設連線
			CloseableHttpClient httpClient = HttpClients.createDefault();
			//建立HttpGet物件,處理get請求,轉發到A站點
			HttpGet httpGet = new HttpGet("http://a.a.com:8080/A/FromServlet?userName="+req.getParameter("userName")); 
                        //執行
			CloseableHttpResponse response = httpClient.execute(httpGet);
			int code = response.getStatusLine().getStatusCode();
			//獲取狀態
			System.out.println("http請求結果為:"+code);
			if(code == 200){
                                //獲取A站點返回的結果
				String result = EntityUtils.toString(response.getEntity());
				System.out.println(result);
                                //把結果返回給B站點
				resp.getWriter().print(result);
			}
			response.close();
			httpClient.close();
		} catch (Exception e) {
		}
	}
}

解決方式4:使用nginx搭建企業級介面閘道器方式

www.a.a.com不能直接請求www.b.b.com的內容,可以通過nginx,根據同域名,但專案名不同進行區分。什麼意思呢?這麼說可能有點抽象。假設我們公司域名叫www.nginxtest.com

當我們需要訪問www.a.a.com通過www.nginxtest.com/A訪問,並通過nginx轉發到www.a.a.com

當我們需要訪問www.b.b.com通過www.nginxtest.com/B訪問,並通過nginx轉發到www.a.a.com

我們訪問公司的域名時,是"同源"的,只是專案名不同,此時專案名的作用只是為了區分,方便轉發。如果你還不理解的話,先看看我是怎麼進行配置的:

server {
        listen       80;
        server_name  www.nginxtest.com;
        location /A {
		    proxy_pass  http://a.a.com:81;
			index  index.html index.htm;
        }
		location /B {
		    proxy_pass  http://b.b.com:81;
			index  index.html index.htm;
        }
    }

我們訪問以www.nginxtest.com開頭且埠為80的網址,nginx將會進行攔截匹配,若專案名為A,則分發到a.a.com:81。實際上就是通過"同源"的域名,不同的專案名進行區分,通過nginx攔截匹配,轉發到對應的網址。整個過程,兩次請求,第一次請求nginx伺服器,第二次nginx伺服器通過攔截匹配分發到對應的網址。

解決方式5:使用Spring Cloud zuul介面閘道器