標籤:

jsonp跨域請求詳解——從繁至簡

什麼是jsonp?為什麼要用jsonp?

JSONP(JSON with Padding)是JSON的一種「使用模式」,可用於解決主流瀏覽器的跨域數據訪問的問題。由於同源策略,一般來說位於 server1.example.com 的網頁無法與不是 server1.example.com的伺服器溝通,而 HTML 的<script> 元素是一個例外。利用 <script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。

以上是百度百科對於jsonp的解釋,所謂"同源"指的是"相同協議相同埠"。

例如:h ttp://abc.com/test1 與以下url做同源檢測

h ttps://abc.com/test2不同源(協議不同 http / https)

h ttp://abc.com:81/test3 不同源(埠不同 80 / 81)

h ttp://abc.com/test4 同源

註:ie"同源策略"不包括埠,即與 h ttp://abc.com:81/test3 也為同源

跨域請求的原理

我們做一個簡單的測試!

我們在 h ttp://169.254.200.238:8020/jsonp/index.html下向

h ttp://169.254.200.238:8080/jsonp.do發起請求,

$.get("http://169.254.200.238:8080/jsonp.do", function (data) { console.log(data); });

此時瀏覽器拋出異常

因為兩者的埠號分別為8080、8020 並不同源,從報錯中也可以看出。

但是,我們換一種方式請求:

<script type="text/javascript" src="http://169.254.200.238:8080/jsonp.do"> </script>

可以看到,此時同樣的請求確成功了!由此,我們可以得出<scrpit>可以進行跨域請求,這是jsonp的基礎,但是瀏覽器同樣拋出了語句不合法的異常,

那是因為我們請求的數據會立馬被瀏覽器當作javascript語句去執行(誰讓我們用<script>去請求數據呢),但是請求到的數據格式並不符合其語法規範。

那麼,如何解決這一問題呢?

如果我們返回的內容符合javascript的語法規範呢?

所以,我們把請求的數據當作一個函數的參數,並且這個函數在客戶端存在的話,那麼這就行得通。

例如:請求返回的數據為

callback( {"result":"success"} )

其中{"result":"success"} 是我們想要獲取的數據,瀏覽器會立即執行callback這個函數,此時,我們已經定義好了函數名為callback這個函數:

function callback(data){ // data為返回數據 // TODO 解析數據}

這樣是不是一切都說的通了!

所以jsonp跨域請求的關鍵就在於:

服務端要在返回的數據外層包裹一個客戶端已經定義好的函數

ajax跨域請求實例

理解了上述內容,你就已經掌握了跨域請求的原理,那麼下面我們將利用ajax發起跨域請求,服務端通過spring MVC處理jsonp請求。

//通過JQuery Ajax 發起jsonp請求(註:不是必須通過jq發起請求 , 例如:Vue Resource中提供 $.http.jsonp(url, [options]))$.ajax({ // 請求方式 type: "get", // 請求地址 url: "http://169.254.200.238:8080/jsonp.do", // 標誌跨域請求 dataType: "jsonp", // 跨域函數名的鍵值,即服務端提取函數名的鑰匙(默認為callback) jsonp: "callbackparam", // 客戶端與服務端約定的函數名稱 jsonpCallback: "jsonpCallback", // 請求成功的回調函數,json既為我們想要獲得的數據 success: function(json) { console.log(json); }, // 請求失敗的回調函數 error: function(e) { alert("error"); }});

@RequestMapping({"/jsonp.do"})public String jsonp(@RequestParam("callbackparam") String callback){ // callback === "jsonpCallback"return callback + "({"result":"success"})";}

此時,客戶端接收到的返回值為:

這就是一個完整的跨域請求,但是這樣就結束了嗎?

細心的同學可能會發覺,現在如果不通過跨域去請求jsonp.do這個介面,不是就報錯了嗎?

是的,現在這個介面僅僅只能被攜帶callbackparam這個參數的請求。

為了解決這個問題,我們要判斷請求的來源,

return (request.from === jsonp) ? callback(data) : data ;

這才是我們想要的結果,解決這個問題需要我們對每個介面都做判斷,或者通過AOP等等方式實現統一處理,這樣做好像並不優雅。

在spring4.2以上的版本,支持了CORS(跨域資源共享)

CORS

CORS是一個W3C標準,全稱是」跨域資源共享」(Cross-origin resource sharing)。

它允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

為什麼說它優雅呢?

整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。

因此,實現CORS通信的關鍵是伺服器。 只要伺服器實現了CORS介面 ,就可以跨源通信。

所以我們客戶端可以像什麼都沒發生一樣,依舊漠不關心的發送我們的請求:

// 不用擔心跨域問題$.ajax({ // 請求方式 type: "get", // 請求地址 url: "http://169.254.200.238:8080/jsonp.do", // 此時依然請求json格式數據 而非jsonp dataType: "json", // 請求成功的回調函數 success: function(json) { console.log(json); }, // 請求失敗的回調函數 error: function(e) { alert("error"); }});

而我們用spring MVC實現的服務端也出乎意料的簡潔(基於xml):

// spring配置文件 spring必須為4.2以上版本<mvc:cors> <mvc:mapping path="/**" /></mvc:cors>

注意:通過以上兩步我們已經完成了跨域請求操作。

這裡,我對整個項目添加了跨域支持 , path 為支持跨域的路徑 , 同樣你可以在這裡做詳細配置:

<mvc:cors> <mvc:mapping path="/api/**" allowed-origins="http://domain1.com, http://domain2.com" allowed-methods="GET, PUT" allowed-headers="header1, header2, header3" exposed-headers="header1, header2" allow-credentials="false" max-age="123" /> <mvc:mapping path="/resources/**" allowed-origins="http://domain1.com" /></mvc:cors>

當然,spring也支持通過java的方式進行配置:

// 此種方式等同於xml全局配置@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); }}// 此種方式為詳細配置@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://domain2.com") .allowedMethods("PUT", "DELETE") .allowedHeaders("header1", "header2", "header3") .exposedHeaders("header1", "header2") .allowCredentials(false).maxAge(3600); }}

spring 也允許針對某個controller類 或者 方法進行 跨域 ,通過@Configuration註解完成。

這裡就不做詳細解釋,詳情可以參考

SpringMvc解決跨域問題 - 王念博客 - 開源中國社區


推薦閱讀:

Spring Security(一) -- 初識Spring Security
Spring Security(二) -- Spring Security的Filter
史上最簡單的SpringCloud教程 | 第八篇: 消息匯流排(Spring Cloud Bus)
Spring Boot中使用Flyway來管理資料庫版本
Spring Security源碼分析五:Spring Security實現簡訊登錄

TAG:Spring | Ajax | 跨域 |