隨著軟件開(kāi)發(fā)分工趨于精細(xì),前后端開(kāi)發(fā)分離成為趨勢(shì)疫衩,前端同事負(fù)責(zé)前端頁(yè)面的展示及頁(yè)面邏輯處理马昙,服務(wù)端同事負(fù)責(zé)業(yè)務(wù)邏輯處理同時(shí)通過(guò)API為前端提供數(shù)據(jù)也為前端提供數(shù)據(jù)的持久化能力榄檬,考慮到前后端同事開(kāi)發(fā)工具和習(xí)慣的不同卜范,必然需要將前后端項(xiàng)目進(jìn)行獨(dú)立,再者考慮到網(wǎng)站訪問(wèn)速度的問(wèn)題鹿榜,需要將靜態(tài)資源部署到CDN服務(wù)器上這樣項(xiàng)目分離也成為了必然海雪。然而項(xiàng)目分離部署分離帶來(lái)的問(wèn)題就是跨域請(qǐng)求的問(wèn)題,本例對(duì)比較流行的兩種跨域訪問(wèn)方式(Jsonp和CORS)進(jìn)行討論舱殿。
一奥裸、介紹
1.1、JSONP
JSONP是利用瀏覽器對(duì)script的資源引用沒(méi)有同源限制沪袭,通過(guò)動(dòng)態(tài)插入一個(gè)script標(biāo)簽湾宙,當(dāng)資源加載到頁(yè)面后會(huì)立即執(zhí)行的原理實(shí)現(xiàn)跨域的。JSONP是一種非正式傳輸協(xié)議冈绊,該協(xié)議的一個(gè)要點(diǎn)就是允許用戶傳遞一個(gè)callback或者開(kāi)始就定義一個(gè)回調(diào)方法侠鳄,參數(shù)給服務(wù)端,然后服務(wù)端返回?cái)?shù)據(jù)時(shí)會(huì)將這個(gè)callback參數(shù)作為函數(shù)名來(lái)包裹住JSON數(shù)據(jù)死宣,這樣客戶端就可以隨意定制自己的函數(shù)來(lái)自動(dòng)處理返回?cái)?shù)據(jù)了伟恶。
JSONP只支持GET請(qǐng)求而不支持POST等其它類型的HTTP請(qǐng)求,它只支持跨域HTTP請(qǐng)求這種情況,不能解決不同域的兩個(gè)頁(yè)面之間如何進(jìn)行JavaScript調(diào)用的問(wèn)題毅该,JSONP的優(yōu)勢(shì)在于支持老式瀏覽器博秫,弊端也比較明顯:需要客戶端和服務(wù)端定制進(jìn)行開(kāi)發(fā)潦牛,服務(wù)端返回的數(shù)據(jù)不能是標(biāo)準(zhǔn)的Json數(shù)據(jù),而是callback包裹的數(shù)據(jù)挡育。
1.2巴碗、CORS
CORS是現(xiàn)代瀏覽器支持跨域資源請(qǐng)求的一種方式,全稱是"跨域資源共享"(Cross-origin resource sharing)即寒,當(dāng)使用XMLHttpRequest發(fā)送請(qǐng)求時(shí)橡淆,瀏覽器發(fā)現(xiàn)該請(qǐng)求不符合同源策略,會(huì)給該請(qǐng)求加一個(gè)請(qǐng)求頭:Origin蒿叠,后臺(tái)進(jìn)行一系列處理明垢,如果確定接受請(qǐng)求則在返回結(jié)果中加入一個(gè)響應(yīng)頭:Access-Control-Allow-Origin;瀏覽器判斷該相應(yīng)頭中是否包含Origin的值,如果有則瀏覽器會(huì)處理響應(yīng)市咽,我們就可以拿到響應(yīng)數(shù)據(jù),如果不包含瀏覽器直接駁回抵蚊,這時(shí)我們無(wú)法拿到響應(yīng)數(shù)據(jù)施绎。
CORS與JSONP的使用目的相同,但是比JSONP更強(qiáng)大贞绳,CORS支持所有的瀏覽器請(qǐng)求類型谷醉,承載的請(qǐng)求數(shù)據(jù)量更大,開(kāi)放更簡(jiǎn)潔冈闭,服務(wù)端只需要將處理后的數(shù)據(jù)直接返回俱尼,不需要再特殊處理。
二萎攒、跨域解決方案距離
2.1遇八、JSONP方案實(shí)現(xiàn)跨域
前段AJAX請(qǐng)求
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;"> 1 $.ajax({
2 url: "http://otherdomain.com/manage/role/get",
3 async: false,
4 type: "get", 5 dataType: "jsonp",
6 data:{
7 "id":1
8 },
9 jsonp: "callback", 10 jsonpCallback:"fn", 11 success: function(data){ 12 alert(data.code); 13 }, 14 error: function(){ 15 alert('fail'); 16 } 17 })</pre>
](javascript:void(0); "復(fù)制代碼")
服務(wù)端響應(yīng)數(shù)據(jù)
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;"> 1 @RequestMapping("/manage/role/get")
2 @ResponseBody
3 public String get(HttpServletRequest request, HttpServletResponse response) { 4 BaseOutput outPut = new BaseOutput(); 5 try { 6 QueryFilter filter = new QueryFilter(request); 7 logger.info(filter.toString());
8 String id = filter.getParam().get(MainConst.KEY_ID); 9 if(!StringUtil.isEmpty(id)) { 10 ImRole role = roleService.getByPk(filter); 11 outPut.setData(role); 12 } 13 else { 14 outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL); 15 outPut.setMsg("The get id is needed."); 16 } 17 } catch (Exception e) { 18 logger.error("獲取角色數(shù)據(jù)異常!", e); 19 outPut.setCode(OutputCodeConst.UNKNOWN_ERROR); 20 outPut.setMsg("獲取角色數(shù)據(jù)異常耍休! " + e.getMessage()); 21 } 22 return "fn("+JsonUtil.objectToJson(outPut)+")"; 23 }</pre>
](javascript:void(0); "復(fù)制代碼")
注意內(nèi)容:
1刃永、Ajax請(qǐng)求需要設(shè)置請(qǐng)求類型為Jsonp
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">dataType: "jsonp"</pre>
2、Ajax請(qǐng)求需要設(shè)置回調(diào)函數(shù)羊精,當(dāng)前函數(shù)值必須與服務(wù)器響應(yīng)包含的callback名稱相同
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">jsonpCallback:"fn"</pre>
3斯够、Ajax請(qǐng)求可以設(shè)置jsonp(可選),傳遞給請(qǐng)求處理程序或頁(yè)面喧锦,用以獲得jsonp回調(diào)函數(shù)名的參數(shù)名读规,默認(rèn)為:callback
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">jsonp: "callback"</pre>
4、服務(wù)端返回Json數(shù)據(jù)必須使用jsonpCallback設(shè)置的值進(jìn)行包裹
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">return "fn("+JsonUtil.objectToJson(outPut)+")"</pre>
2.2燃少、CORS方案實(shí)現(xiàn)跨域
前段AJAX請(qǐng)求
[[圖片上傳失敗...(image-f88c85-1528237020998)]](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;"> 1 function test() { 2 $.ajax({
3 url: "http://localhost:8080/AdsServer/manage/role/get",
4 type: "get",
5 async: false,
6 data:{
7 "id":1
8 },
9 dataType:"json", 10 withCredentials:true, 11 success: function(data){ 12 alert(data); 13 alert(data.code); 14 }, 15 error: function(){ 16 alert('fail'); 17 } 18 }) 19 }</pre>
[[圖片上傳失敗...(image-d70dd4-1528237020998)]](javascript:void(0); "復(fù)制代碼")
服務(wù)端響應(yīng)數(shù)據(jù)
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;"> 1 @RequestMapping("/manage/role/get")
2 @ResponseBody
3 public String get(HttpServletRequest request, HttpServletResponse response) { 4 BaseOutput outPut = new BaseOutput(); 5 try { 6 QueryFilter filter = new QueryFilter(request); 7 logger.info(filter.toString());
8 String id = filter.getParam().get(MainConst.KEY_ID); 9 if(!StringUtil.isEmpty(id)) { 10 ImRole role = roleService.getByPk(filter); 11 outPut.setData(role); 12 } 13 else { 14 outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL); 15 outPut.setMsg("The get id is needed."); 16 } 17 } catch (Exception e) { 18 logger.error("獲取角色數(shù)據(jù)異常束亏!", e); 19 outPut.setCode(OutputCodeConst.UNKNOWN_ERROR); 20 outPut.setMsg("獲取角色數(shù)據(jù)異常! " + e.getMessage()); 21 } 22 return JsonUtil.objectToJson(outPut); 23 }</pre>
](javascript:void(0); "復(fù)制代碼")
服務(wù)端增加過(guò)濾攔截器(web.xml)
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">1 <filter>
2 <filter-name>crossDomainFilter</filter-name>
3 <filter-class>com.luwei.core.filter.CrossDomainFilter</filter-class>
4 </filter>
5 <filter-mapping>
6 <filter-name>crossDomainFilter</filter-name>
7 <url-pattern>*</url-pattern>
8 </filter-mapping></pre>
](javascript:void(0); "復(fù)制代碼")
服務(wù)端增加過(guò)濾攔截器(java)
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;"> 1 package com.luwei.core.filter; 2
3 import java.io.IOException; 4
5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13
14 import org.apache.commons.lang.StringUtils; 15 import org.slf4j.Logger; 16 import org.slf4j.LoggerFactory; 17
18 import com.luwei.console.mg.constant.ApplicationConfiConst; 19
20 /**
21 * 22 * <Description> TODO
23 * 24 * @author lu.wei
25 * @email 1025742048@qq.com
26 * @date 2017年1月4日
27 * @since V1.0
28 * @see com.luwei.console.mg.tag
29 */
30 public class CrossDomainFilter implements Filter { 31 private Logger logger = LoggerFactory.getLogger(getClass()); 32
33 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 34 ApplicationConfiConst confiConst = (ApplicationConfiConst) ContextUtil.getBean("applicationConfiConst"); 35 HttpServletResponse response = (HttpServletResponse) res; 36 HttpServletRequest request = (HttpServletRequest) req; 37 String referer = request.getHeader("referer"); 38 String origin = null; 39 if (null != referer) { 40 String[] domains = confiConst.getCanAccessDomain().split(","); 41 for (String domain : domains) { 42 if (StringUtils.isNotEmpty(domain) && referer.startsWith(domain)) { 43 origin = domain; 44 break; 45 } 46 } 47 } 48 response.setHeader("Access-Control-Allow-Origin", origin); 49 response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PATCH"); 50 response.setHeader("Access-Control-Max-Age", "3600"); 51 response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); 52 // 是否支持cookie跨域
53 response.addHeader("Access-Control-Allow-Credentials", "true"); 54
55 String requestURI = ((HttpServletRequest) req).getRequestURI(); 56 long begin = System.currentTimeMillis(); 57 chain.doFilter(req, res); 58 if (logger.isDebugEnabled()) { 59 logger.debug("[Request URI: " + requestURI + "], Cost Time:" + (System.currentTimeMillis() - begin) + "ms"); 60 } 61 } 62 }</pre>
](javascript:void(0); "復(fù)制代碼")
增加設(shè)置能夠通過(guò)跨域訪問(wèn)的服務(wù)器地址
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">#設(shè)置能夠訪問(wèn)接口的域(多個(gè)通過(guò)都好分割)(不能配置127.0.0.1)
CAN_ACCESS_DOMAIN=http://localhost:8020,http://localhost:9999,http://localhost:8080</pre>
注意內(nèi)容:
1供汛、Ajax請(qǐng)求必須要設(shè)置withCredentials屬性為true
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">withCredentials:true</pre>
2枪汪、服務(wù)端需要配置過(guò)濾器涌穆,講配置能夠進(jìn)行跨域訪問(wèn)服務(wù)器的地址進(jìn)行配置
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); // 是否支持cookie跨域
response.addHeader("Access-Control-Allow-Credentials", "true");</pre>
3、withCredentials設(shè)置成true時(shí)雀久,Access-Control-Allow-Origin不支持通過(guò)*的方式進(jìn)行統(tǒng)配
4宿稀、Access-Control-Allow-Origin不能直接配置多個(gè)請(qǐng)求服務(wù)器,但是可以通過(guò)靜態(tài)配置多個(gè)的方式赖捌,然后根據(jù)referer匹配祝沸,匹配到哪個(gè)則設(shè)置Access-Control-Allow-Origin為哪個(gè)的方式來(lái)配置多個(gè)
后記:
jqGrid配置跨域請(qǐng)求的方式為:
<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; word-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">ajaxGridOptions: {
xhrFields: {
withCredentials: true }
},</pre>