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://http://www.abc.com/test1 與以下url做同源檢測
h ttps://http://www.abc.com/test2不同源(協議不同 http / https)
h ttp://http://www.abc.com:81/test3 不同源(埠不同 80 / 81)
h ttp://http://www.abc.com/test4 同源
註:ie"同源策略"不包括埠,即與 h ttp://http://www.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實現簡訊登錄