web開發(fā)隨著ajax的出來帶來了革命性的變化,它改變了web的數(shù)據(jù)加載方式讓交互更友好,網(wǎng)絡資源更節(jié)省。但最初ajax考慮安全性并沒有開放跨域請求,隨著H5的到來ajax開放了跨域請求,所以ajax跨域請求存在兼容性,不過現(xiàn)在的瀏覽器大部分都已經(jīng)支持了。
常用跨域請求手段有:
jsonp
FORM到頁面框架
HTTP服務器代理
服務器腳本轉(zhuǎn)發(fā)
ajax
WebSocket
插件
jsonp
這種方式是早期在ajax不支持跨域請求時的一種替代方案應用非常多,在JQuery類的早期框架都集成了此功能。期原理就是通過HTML的<script>標簽加載一個跨域的請求地址并指定一個隨機回調(diào)函數(shù),所連接的服務器返回指定的回調(diào)函數(shù)并增加參數(shù),標簽加載完后會自動執(zhí)行代碼來完成請求回調(diào),因此jsonp只支持GET請求方式,并且需要服務器作專用處理,典型的示例如:
前端代碼:(域名www.a.cn請求域名www.b.cn)
(function?(global)?{ ????//發(fā)送請求 ????function?request(url,?data,?callback)?{ ????????function?jsonpcall(_data,?error)?{ ????????????callback(_data,?error); ????????????//清理 ????????????delete?global[data['jsonpcallback']]; ????????????document.body.removeChild(script); ????????} ????????data?=?data?||?{}; ????????//生成全局回調(diào)函數(shù) ????????data['jsonpcallback']?=?'jsonp_'?+?String(Math.random()).substr(2,?10); ????????global[data['jsonpcallback']]?=?jsonpcall; ????????var?params?=?getParamsString(data); ????????var?script?=?document.createElement('script'); ????????script.type?=?'text/javascript'; ????????script.src?=?url?+?(url.indexOf('?')?>=?0???'&'?:?'?')?+?params; ????????script.onerror?=?function?(event)?{ ????????????jsonpcall(null,?event); ????????}; ????????document.body.appendChild(script); ????} ????//轉(zhuǎn)換請求參數(shù) ????function?getParamsString(data,?prefix)?{ ????????var?arr?=?[]; ????????prefix?=?prefix?||?''; ????????for?(var?key?in?data)?{ ????????????var?name?=?''; ????????????if?(!prefix)?{ ????????????????name?=?key; ????????????}?else?{ ????????????????name?=?prefix?+?'['?+?key?+?']'; ????????????} ????????????if?(typeof?data[key]?===?'object')?{ ????????????????arr.push(getParamsString(data[key],?name)); ????????????}?else?{ ????????????????arr.push(name?+?'='?+?data[key]); ????????????} ????????} ????????return?arr.join('&'); ????} ????global.jsonp?=?request; })(window); //發(fā)起請求 jsonp('http://www.b.cn',?{id:?12},?function?()?{ ????console.info(arguments); });
服務端代碼:(域名www.b.cn,以PHP為例)
<?php if?(empty($_GET['jsonpcallback']))?{ ????header('HTTP/1.1?404?Not?Found'); ????die(); } //查數(shù)據(jù),返回結(jié)果 $data?=?[ ????'status'?=>?'ok', ????'msg'?=>?'操作成功' ]; //輸出結(jié)果 echo?$_GET['jsonpcallback']?.?'('?.?json_encode($data,?JSON_UNESCAPED_UNICODE)?.?');';
這種方式請求不受跨域限制使用方便,兼容性好,非常適用小數(shù)據(jù)量跨域請求。缺點是只支持GET請求無法完成像上傳文件或其它請求方式的操作,服務器響應結(jié)果專用性強。
FORM到頁面框架
這種方式在異步上傳文件使用非常多,通常是iframe+form結(jié)合通過form的target屬性指定到iframe的name完成異步請求,但iframe有部分移動端存瀏覽器不支持,另外還有一個frameset標簽在H5中已經(jīng)不支持。
使用這種方式跨域請求兼容性僅僅在PC端較好而且服務端不需要作額外處理,但移動端上使用需要留意用戶平臺是否都支持。典型的示例如:
//?請求處理 function?request(form,?callback)?{ ????var?iframe?=?document.createElement('iframe'),?name?=?'IFRAME-'?+?String(Math.random()).replace('.',?''),?submit?=?1; ????iframe.name?=?name,?form.target?=?name; ????iframe.onload?=?function?()?{ ????????//初次加載 ????????if?(submit?===?1)?{ ????????????form.submit(); ????????}?else?if?(submit?===?2)?{ ????????????try?{ ????????????????var?json?=?JSON.parse((iframe.contentDocument?||?iframe.contentWindow.document).body.innerHTML); ????????????????callback(json); ????????????}?catch?(err)?{ ????????????????callback(null,?err); ????????????} ????????????document.body.removeChild(iframe); ????????} ????????submit++; ????}; ????iframe.onerror?=?function?(event)?{ ????????callback(null,?event); ????????document.body.removeChild(iframe); ????}; ????//不能顯示標簽 ????iframe.style.display?=?'none'; ????document.body.appendChild(iframe); } //發(fā)送請求 request(document.forms[0],?function(){ ????console.info(arguments); });
這種方式間接完成跨域操作,同時對服務端的代碼沒有額外要求,只需要返回一個通用的json串即可,通過這個方式可以完成GET與POST請求并且還可以上傳文件,大的缺點是有少部分瀏覽器不支持。
HTTP服務器代理
這種方式從理論上說不需要開發(fā)額外的代碼只需要在HTTP服務器上配置代理轉(zhuǎn)發(fā)即可以滿足所有請求的跨域請求,但會額外增加服務器性能開銷,不適合于跨域請求過多的場景,畢竟服務器的資源是有限的。一般使用這種方式的基本上是小項目,大項目這么玩在硬件上開銷不容忽視。以nginx為例,典型的配置代碼有:(僅以server塊為例)
????location?/proxy?{ ??????????#?resolver?114.114.114.114?223.5.5.5?valid=3600s; ??????????proxy_pass?http://www.b.com:8099/; ????}
nginx的proxy_pass做代理配置很容易,配置靈活。
如果在proxy_pass中指定了詳細地址則只轉(zhuǎn)發(fā)到對應的地址
如果沒有指定地址則把當前的地址追加到域名下
如果需要指定DNS解析地址則可以增加resolver命令,但一定要保證DNS服務器正常,否則請求會卡住
轉(zhuǎn)發(fā)可以使用IP地址和域名,如果有特定端口一定要追加上
域名可以使用變量來傳入,例如:set $proxy_host "http://www.b.com:8099/";? proxy_pass $proxy_host;
可以指定路徑,也可以通過if來判斷
nginx配置代理調(diào)試比較麻煩需要多注意兩個日志文件。對于配置要考慮:
是否被截取即沒有進入代理塊
代理域名端口協(xié)議是否正確
域名或地址是否能正常訪問
服務器腳本轉(zhuǎn)發(fā)
這種方式是通過服務器的腳本來轉(zhuǎn)發(fā),避開前端跨域請求,開發(fā)方便,對于配置HTTP服務器有限制或問題時是一個比較簡單的替代方案。本質(zhì)與HTTP服務器代理類似,都是通過服務器來轉(zhuǎn)來,不可避免增加服務器的開銷。這種處理方式唯一的好處是開發(fā)調(diào)試容易,兼容性好。以PHP為例,典型的示例代碼如:
$url?=?$_GET['_url_']; $input?=?file_get_contents('php://input'); $curl?=?curl_init(); curl_setopt_array($curl,?[ ????CURLOPT_HTTP_VERSION?=>?CURL_HTTP_VERSION_1_1, ????CURLOPT_USERAGENT?=>?'Mozilla/5.0?(Windows?NT?10.0;?WOW64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/41.0.2272.118?Safari/537.36', ????CURLOPT_CONNECTTIMEOUT?=>?10, ????CURLOPT_TIMEOUT?=>?30, ????CURLOPT_RETURNTRANSFER?=>?true, ????CURLOPT_SSL_VERIFYPEER?=>?FALSE,?//?不驗證證書 ????CURLOPT_SSL_VERIFYHOST?=>?FALSE,?//?不驗證域名 ]); if?($_SERVER['REQUEST_METHOD']?==?'POST')?{ ????curl_setopt($curl,?CURLOPT_POST,?true); ????curl_setopt($curl,?CURLOPT_POSTFIELDS,?$input); ????curl_setopt($curl,?CURLOPT_URL,?$url); }?else?{ ????unset($_GET['_url_']); ????$params?=?http_build_query($_GET); ????curl_setopt($curl,?CURLOPT_URL,?$url?.?(strpos($url,?'?')???'&'?:?'?')?.?$params); } $response?=?curl_exec($curl); $httpCode?=?curl_getinfo($curl,?CURLINFO_HTTP_CODE); curl_close($curl); header('HTTP/1.1?'?.?$httpCode); echo?$response;
代碼中簡單的示例通過curl發(fā)起跨域請求,前端只需要指定需要請求的跨域地址和參數(shù)即可完成請求操作,由于請求類型多樣化,所以使用代碼轉(zhuǎn)發(fā)要做到兼容所有請求形式則需要做較多的處理,一般用不上。
ajax
ajax支持跨域可以說可以非常理想,遺憾的時推出的晚,不過現(xiàn)在隨著瀏覽器全面支持H5使得ajax跨域請求變得普及起來,如果應用平臺涉及比較老的瀏覽器則需要留意了。官方文檔:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS?在官方文檔中有比較詳細的說明。
其實從本質(zhì)上說跨域限制只是瀏覽器出于安全考慮的一個限制,而開放這個限制也需要合理安全,否則會造成比較多的跨域請求漏洞,因此瀏覽器對ajax跨域請求作了一些限制,它必需要服務器返回允許跨域響應頭信息,才正常提供響應結(jié)果,所以ajax跨域請求需要服務端額外增加響應頭信息。
ajax跨域請求分為兩種場景:基本請求,預檢請求;并且XMLHttpRequest內(nèi)部通過指定條件強制判斷識別并作出相應的操作,其中預檢請求會觸發(fā)CORS預檢,CORS預檢會把請求類型強制修改為OPTIONS類型向服務器發(fā)起跨域請求檢查服務器是否響應允許跨域請求頭信息如果允許則再次發(fā)起指定的請求類型跨域請求并把響應內(nèi)容注入到ajax響應內(nèi)容中,而基本請求是直接發(fā)起指定請求類型的跨域請求。兩種請求場景均需要跨域服務器返回允許跨域響應頭信息,響應狀態(tài)等頭信息不會受影響。
常規(guī)請求
這種場景瀏覽器會直接發(fā)起請求,當響應頭信息內(nèi)容中不包含Access-Control-Allow-Origin允許頭信息時會丟棄響應內(nèi)容并向控制臺發(fā)出Failed to load警告信息,提示服務器沒有Access-Control-Allow-Origin響應頭信息請求不被允許。
條件:(需全部滿足)
請求類型為 GET、HEAD、POST
僅設置過Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width頭信息,或使用默認的不設置頭信息
Content-Type只能設置為application/x-www-form-urlencoded、multipart/form-data、text/plain,或者不設置
在請求中沒有使用過XMLHttpRequestUpload事件監(jiān)聽。XMLHttpRequestUpload是通過XMLHttpRequest對象的upload屬性獲取的一個上傳進度對象,可以獲取上傳進度數(shù)據(jù)。
請求中沒有使用ReadableStream對象。ReadableStream大部分瀏覽器還不支持,是一個獲取響應二進度流的對象處理器。
示例代碼:(只在不觸犯以上任何一個條件即可)
(function?(global)?{ ????function?request(url,?callback,?data,?method)?{ ????????var?xmlHttp?=?new?XMLHttpRequest(); ????????var?params?=?getParamsString(data?||?{}); ????????method?=?method?||?'GET'; ????????if?(method.toUpperCase()?===?'POST')?{ ????????????xmlHttp.open(method,?url,?true); ????????????xmlHttp.setRequestHeader('Content-Type',?'application/x-www-form-urlencoded;'); ????????????xmlHttp.send(params); ????????}?else?{ ????????????xmlHttp.open(method,?url?+?(url.indexOf('?')?>=?0???'&'?:?'?')?+?params,?true); ????????????xmlHttp.send(null); ????????} ????????xmlHttp.onreadystatechange?=?function?()?{ ????????????if?(xmlHttp.readyState?==?4)?{ ????????????????try?{ ????????????????????if?(xmlHttp.status?==?200)?{ ????????????????????????var?json?=?JSON.parse(xmlHttp.responseText); ????????????????????????callback(json); ????????????????????}?else?{ ????????????????????????callback(null,?xmlHttp.responseText); ????????????????????} ????????????????}?catch?(err)?{ ????????????????????callback(null,?err); ????????????????} ????????????} ????????}; ????} ????//轉(zhuǎn)換請求參數(shù) ????function?getParamsString(data,?prefix)?{ ????????var?arr?=?[]; ????????prefix?=?prefix?||?''; ????????for?(var?key?in?data)?{ ????????????var?name?=?''; ????????????if?(!prefix)?{ ????????????????name?=?key; ????????????}?else?{ ????????????????name?=?prefix?+?'['?+?key?+?']'; ????????????} ????????????if?(typeof?data[key]?===?'object')?{ ????????????????arr.push(getParamsString(data[key],?name)); ????????????}?else?{ ????????????????arr.push(name?+?'='?+?data[key]); ????????????} ????????} ????????return?arr.join('&'); ????} ????global.ajax?=?request; })(window); //發(fā)起請求 ajax('https://www.b.cn/',?function?()?{ ????console.info(arguments); });
預檢請求
這種場景請求瀏覽器會強制把請求類型改為OPTIONS類型發(fā)起預檢跨域請求,當響應頭信息內(nèi)容中不包含Access-Control-Allow-Origin允許頭信息時會丟棄響應內(nèi)容并向控制臺發(fā)出Failed to load警告信息,提示服務器沒有Access-Control-Allow-Origin響應頭信息請求不被允許;如果響應頭信息全部允許跨域請求則瀏覽器會再次發(fā)送指定請求類型的跨域請求到服務器并獲取本次響應內(nèi)容注入到ajax的響應內(nèi)容中。
條件:(任意條滿足)
請求類型指定為:PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
設置了Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width之外的頭信息
設置Content-Type是為application/x-www-form-urlencoded、multipart/form-data、text/plain之外的類型
在請求中使用過XMLHttpRequestUpload事件監(jiān)聽
請求中使用ReadableStream對象
示例代碼:(強制增加一個特殊頭信息即可)
(function?(global)?{ ????function?request(url,?callback,?data,?method)?{ ????????var?xmlHttp?=?new?XMLHttpRequest(); ????????var?params?=?getParamsString(data?||?{}); ????????method?=?method?||?'GET'; ????????if?(method.toUpperCase()?===?'POST')?{ ????????????xmlHttp.open(method,?url,?true); ????????????xmlHttp.setRequestHeader('Ajax-Request',?'1'); ????????????xmlHttp.setRequestHeader('Content-Type',?'application/x-www-form-urlencoded;'); ????????????xmlHttp.send(params); ????????}?else?{ ????????????xmlHttp.open(method,?url?+?(url.indexOf('?')?>=?0???'&'?:?'?')?+?params,?true); ????????????xmlHttp.setRequestHeader('Ajax-Request',?'1'); ????????????xmlHttp.setRequestHeader('Content-Type',?'text/html'); ????????????xmlHttp.send(null); ????????} ????????xmlHttp.onreadystatechange?=?function?()?{ ????????????if?(xmlHttp.readyState?==?4)?{ ????????????????try?{ ????????????????????if?(xmlHttp.status?==?200)?{ ????????????????????????var?json?=?JSON.parse(xmlHttp.responseText); ????????????????????????callback(json); ????????????????????}?else?{ ????????????????????????callback(null,?xmlHttp.responseText); ????????????????????} ????????????????}?catch?(err)?{ ????????????????????callback(null,?err); ????????????????} ????????????} ????????}; ????} ????//轉(zhuǎn)換請求參數(shù) ????function?getParamsString(data,?prefix)?{ ????????var?arr?=?[]; ????????prefix?=?prefix?||?''; ????????for?(var?key?in?data)?{ ????????????var?name?=?''; ????????????if?(!prefix)?{ ????????????????name?=?key; ????????????}?else?{ ????????????????name?=?prefix?+?'['?+?key?+?']'; ????????????} ????????????if?(typeof?data[key]?===?'object')?{ ????????????????arr.push(getParamsString(data[key],?name)); ????????????}?else?{ ????????????????arr.push(name?+?'='?+?data[key]); ????????????} ????????} ????????return?arr.join('&'); ????} ????global.ajax?=?request; })(window); //發(fā)起請求 ajax('https://www.b.cn/',?function?()?{ ????console.info(arguments); });
攜帶請求資源
在跨域請求時還允許攜帶請求資源如cookie,但必需設置XMLHttpRequest對象的withCredentials屬性為true,如:
xmlHttp.withCredentials?=?true;
攜帶的請求資源受,響應頭信息Access-Control-Allow-Origin、Access-Control-Allow-Credentials兩個影響,Access-Control-Allow-Origin必需指定為當前請求的域名,Access-Control-Allow-Credentials必需設置為true否則跨域請求攜帶資源失敗即服務器返回的cookie不會被記錄。跨域請求只受Access-Control-Allow-Origin影響,只要Access-Control-Allow-Origin允許當前域名則請求可以正常獲取響應結(jié)果。
注意:如果設置這個值,Access-Control-Allow-Origin指定為通配符有兼容問題,需要設置為對應的域名集。
跨域請求響應頭信息
這些頭信息由服務器響應,瀏覽器內(nèi)核判斷并作相應處理。這些頭信息是預定好的,只需要按規(guī)則響應返回給瀏覽即可完成跨域請求。瀏覽器對這些頭信息處理略有差異,不建議設計的太隨意畢竟安全還是比較重要的。
Access-Control-Allow-Origin
該頭信息是跨域請求中必需響應的,它用來標識服務器允許哪些請求來源域名共享數(shù)據(jù),一般如果沒有特別要求最好設置為指定的開放域名。如果允許任意域名使用*通配符。如果是指定一個域名并且每次請求該響應頭均會變化則需要增加Vary: Origin頭信息來禁止瀏覽器的緩存。
Access-Control-Expose-Headers
該頭信息允許暴露給外部的頭信息,默認只可以暴露服務器響應Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma頭信息。如果想讓腳本通過XMLHttpRequest對象的getAllResponseHeaders()函數(shù)訪問到其它頭信息則可以在這里設置,多個以逗號分開也可以使用通配符*。注意預檢請求中詢問成功后才有效。
Access-Control-Max-Age
該狀信息用于預檢請求中Access-Control-Allow-Methods、Access-Control-Allow-Headers信息可以緩存多久,單位為秒,每個瀏覽器中大秒數(shù)有限制,火狐中大86400秒,谷歌中600秒,如果需要禁用緩存響應返回-1即可。
Access-Control-Allow-Credentials
該頭信息是允許跨域請求中攜帶請求資源主要是cookie。只要在XMLHttpRequest對象中設置withCredentials=true同時Access-Control-Allow-Origin為當前請求的域名,Access-Control-Allow-Credentials為true(字符串)即可攜帶資源請求。
Access-Control-Allow-Methods
該頭信息是允許跨域請求類型集,可以設置為指定的請求類型如:GET、POST、PUT、DELETE等,也可以設置為*通配符允許所有請求類型,默認不設置即允許所有請求。該頭信息僅在預檢請求中有效,標準要求是當發(fā)送OPTIONS請求時如果響應中允許當前指定請求類型時會自動再發(fā)起一次指定的請求類型請求并對XMLHttpRequest對象作出回調(diào),目前各瀏覽器實現(xiàn)并不統(tǒng)一,有的瀏覽器此參數(shù)無效。
Access-Control-Allow-Headers
該頭信息用于開放額外發(fā)送給服務器的頭信息,默認必定允許Accept、Accept-Language、Content-Language、Content-Type等常規(guī)允許頭信息設置,當指定其它頭信息時瀏覽器啟動預檢請求詢問服務器是否允許額外的這些頭信息設置,如果允許則再發(fā)送常規(guī)請求。開放的頭信息多個以逗號分開也可以使用通配符*(通配符有兼容問題)。如果頭信息不能匹配允許則請求終止不再發(fā)起常規(guī)請求并且XMLHttpRequest獲取響應失敗代碼無法獲取服務器的響應內(nèi)容(有兼容性問題部分瀏覽器還是可以獲取服務器響應內(nèi)容)。
跨域禁止修改請求頭信息
這些請求頭信息在跨域請求時瀏覽器禁止代碼修改。在非跨域請求則允許修改。
Origin
標記請求來源站點。
Access-Control-Request-Method
用于通知服務器真實的請求類型。
Access-Control-Request-Headers
用于通知服務器請求頭字段集,多個以逗號分開或使用通配符*
兼容性
跨域請求是新開放的特性在兼容性方面差異比較多,一般推薦使用常規(guī)請求會更理想,使用預檢模式下很多代碼或服務器均未對OPTIONS請求類型提供支持導致請求失敗。對于瀏覽器內(nèi)核兼容性可查看:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Browser_compatibility
WebSocket
WebSocket是web引入的全新技術,讓web端也能實現(xiàn)長連接,目前兼容性?https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7?。websocket允許跨域連接并且不需要額外處理,因為是長連接,所以web服務器需要額外調(diào)整為長連接才能與終端通信。websocket連接操作簡單,但服務器端需要轉(zhuǎn)換處理方式,一般應用與游戲、直播類項目較多。
兼容性
WebSocket請求是專門為H5設計的,目前支付H5的瀏覽器均支持。在部分瀏覽器中https與http頁面中使用ws與wss有安全限制,要求https只能連接wss,而http的可以使用ws和wss。
一般在https頁面中最好使用wss協(xié)議連接,如果服務端不支持wss可以通過nginx進行轉(zhuǎn)發(fā)把ws改為wss。
插件
增加插件可以擴展web功能。插件不受瀏覽器過多的限制,在跨域請求中可以很好的發(fā)輝。缺點是插件的開發(fā)形式不統(tǒng)一,兼容性也不一樣,目前比較多的是flash,但flash自身的問題已經(jīng)將慢慢進入歷史舞臺了。
另外有需要云服務器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。
網(wǎng)站名稱:WEB跨域請求-創(chuàng)新互聯(lián)
標題URL:http://chinadenli.net/article28/dhigcp.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供App開發(fā)、網(wǎng)站導航、python、小程序開發(fā)、網(wǎng)站設計、網(wǎng)站維護
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容