<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          「深入淺出」前端開發(fā)中常用的幾種跨域解決方案

          共 7224字,需瀏覽 15分鐘

           ·

          2021-01-31 02:56


          編者薦語

          本文將為大家介紹,前端開發(fā)中,最常用的幾種跨域解決方案;

          看完本文可以系統(tǒng)地掌握,不同種跨域解決方案間的巧妙,以及它們的用法、原理、局限性和適用的場景

          包括以下幾個(gè)方面:

          • 跨域的現(xiàn)象,和幾種常見的跨域表現(xiàn)
          • 跨域的解決方案(原理分析)
            • 修改本地HOST
            • JSONP
            • CORS
            • Proxy
            • Nginx反向代理
            • Post Message(利用iframe標(biāo)簽,實(shí)現(xiàn)不同域的關(guān)聯(lián))

          同源是什么?

          如果兩個(gè)URL的協(xié)議protocol、主機(jī)名host和端口號(hào)port都相同的話,則這兩個(gè)URL是同源。

          同源策略

          同源策略是一個(gè)重要的安全策略。它能夠阻斷惡意文檔,減少被攻擊的媒介。

          真實(shí)項(xiàng)目中,很少有同源策略,大部分都是非同源策略

          跨域是什么?

          當(dāng)協(xié)議、域名與端口號(hào)中任意一個(gè)不相同時(shí),都算作不同域,不同域之間相互請(qǐng)求資源的表現(xiàn)(非同源策略請(qǐng)求),稱作”跨域“

          跨域現(xiàn)象

          那么我們就下面的網(wǎng)址分析一下,哪一塊是協(xié)議,哪一塊是域名及端口號(hào)

          http://kbs.sports.qq.com/index.html

          協(xié)議:http(還有一種https協(xié)議)
          域名:kbs.sports.qq.com
          端口號(hào):80

          https://127.0.0.1:3000

          協(xié)議:https
          域名:127.0.0.1
          端口號(hào):3000

          假如我們的真實(shí)項(xiàng)目開發(fā)中的Web服務(wù)器地址為 ”http://kbs.sports.qq.com/index.html“,而需要請(qǐng)求的數(shù)據(jù)接口地址為 "http://api.sports.qq.com/list"。

          當(dāng)Web服務(wù)器的地址向數(shù)據(jù)接口的地址發(fā)送請(qǐng)求時(shí),便會(huì)造成了跨域現(xiàn)象

          造成跨域的幾種常見表現(xiàn)

          • 服務(wù)器分開部署(Web服務(wù)器 + 數(shù)據(jù)請(qǐng)求服務(wù)器)
          • 本地開發(fā)(本地預(yù)覽項(xiàng)目 調(diào)取 測試服務(wù)器的數(shù)據(jù))
          • 調(diào)取第三方平臺(tái)的接口

          Web服務(wù)器:主要用來靜態(tài)資源文件的處理

          解決方案

          • 修改本地HOST(不作介紹)
          • JSONP
          • CORS
          • Proxy
          • Nginx反向代理
          • Post Message(利用iframe標(biāo)簽,實(shí)現(xiàn)不同域的關(guān)聯(lián))

          在后面會(huì)詳細(xì)分析這四種解決方案的原理和用法配置,以及它們的優(yōu)點(diǎn)和局限性

          注意: 基于ajaxfetch發(fā)送請(qǐng)求時(shí),如果是跨域的,則瀏覽器默認(rèn)的安全策略會(huì)禁止該跨域請(qǐng)求

          補(bǔ)充說明:以下所有的測試用例,均由Web:http://127.0.0.1:5500/index.htmlAPI:http://127.0.0.1:1001/list發(fā)起請(qǐng)求

          API接口的服務(wù)器端是自己通過express建立的,下文在服務(wù)器端以app.use中間件的形式接受來自客戶端的請(qǐng)求并做處理。

          即 在“http://127.0.0.1:1001/list”from origin“http://127.0.0.1:55”上對(duì)XMLHttpRequest的訪問已被CORS策略阻止:被請(qǐng)求的資源上沒有“Access- control - allow-origin”頭

          在后端開啟了一個(gè)端口號(hào)為1001的服務(wù)器之后,我們來實(shí)踐一下

          let?xhr?=?new?XMLHttpRequest;
          xhr.open('get',?'http://127.0.0.1:1001/list');
          xhr.onreadystatechange?=?()?=>?{
          ??if?(xhr.status?===?200?&&?xhr.readyState?===?4)?{
          ????console.log(xhr.responseText);
          ??}
          };
          xhr.send();?
          跨域的常見報(bào)錯(cuò)提示

          這就是由于瀏覽器默認(rèn)的安全策略禁止導(dǎo)致的。

          下面介紹一下幾種常見的解決方案。

          JSONP

          原理:JSONP利用script標(biāo)簽不存在域的限制,且定義一個(gè)全局執(zhí)行上下文中的函數(shù)func

          (用來接收服務(wù)器端返回的數(shù)據(jù)信息)來接收數(shù)據(jù),從而實(shí)現(xiàn)跨域請(qǐng)求。

          弊端

          • 只允許GET請(qǐng)求
          • 不安全:只要瀏覽器支持,且存在瀏覽器的全局變量里,則誰都可以調(diào)用

          圖解JSONP的原理

          手動(dòng)封裝JSONP

          callback必須是一個(gè)全局上下文中的函數(shù)

          (防止不是全局的函數(shù),我們需要把這個(gè)函數(shù)放在全局上,并且從服務(wù)器端接收回信息時(shí),要瀏覽器執(zhí)行該函數(shù))

          注意:

          • uniqueName變量存儲(chǔ)全局的回調(diào)函數(shù)(確保每次的callback都具有唯一性)
          • 檢驗(yàn)url中是否含有"?",有的話直接拼接callback,沒有的話補(bǔ)”?“
          //?客戶端
          function?jsonp(url,?callback)?{
          ??//?把傳遞的回調(diào)函數(shù)掛載到全局上
          ?let?uniqueName?=?`jsonp${new?Date().getTime()}`;
          ??//?套了一層?anonymous?function
          ??//?目的讓?返回的callback執(zhí)行且刪除創(chuàng)建的標(biāo)簽
          ??window[uniqueName]?=?data?=>?{
          ??//?從服務(wù)器獲取結(jié)果并讓瀏覽器執(zhí)行callback
          ????document.body.removeChild(script);
          ????delete?window[uniqueName];
          ????callback?&&?callback(data);
          ??}
          ??
          ??//?處理URL
          ??url?+=?`${url.includes('?')}???'&'?:?'?}callback=${uniqueName}'`;
          ??
          ??//?發(fā)送請(qǐng)求
          ??let?script?=?document.createElement('script');
          ??script.src?=?url;
          ??document.body.appendChild(script);
          }

          //?執(zhí)行第二個(gè)參數(shù)?callback,獲取數(shù)據(jù)
          jsonp('http://127.0.0.1:1001/list?userName="lsh"',?(result)?=>?{
          ?console.log(result);
          })
          //?服務(wù)器端
          //?Api請(qǐng)求數(shù)據(jù)
          app.get('/list',?(req,?res)?=>?{
          ??
          ??//?req.query?問號(hào)后面?zhèn)鬟f的參數(shù)信息
          ??//?此時(shí)的callback?為傳遞過來的函數(shù)名字?(uniqueName)
          ?let?{?callback?}?=?req.query;
          ??
          ??//?準(zhǔn)備返回的數(shù)據(jù)(字符串)
          ??let?res?=?{?code:?0,?data:?[10,20]?};
          ??let?str?=?`${callback}($(JSON.stringify(res)))`;
          ??
          ??//?返回給客戶端數(shù)據(jù)
          ??res.send(str);
          })

          測試用例展示:

          • 客戶端請(qǐng)求的url
          • 服務(wù)端返回的數(shù)據(jù)
            • 返回的callback
            • 返回的數(shù)據(jù)信息 result
          //?服務(wù)器請(qǐng)求的?url
          Request?URL:
          ?http://127.0.0.1:1001/list?userName="lsh"&callback=jsonp159876002

          //?服務(wù)器返回的函數(shù)callback
          ?jsonp159876002({"code":0,?"data":?[10,20]});

          //?客戶端接收的數(shù)據(jù)信息
          {?code:?0,?data:?Array(2)?}

          當(dāng)瀏覽器發(fā)現(xiàn)返回的是jsonp159876002({"code":0, "data": [10,20]});這個(gè)函數(shù),會(huì)自動(dòng)幫我們執(zhí)行的。

          JSONP弊端

          在上文中說到只要服務(wù)器端那里設(shè)置了允許通過jsonp的形式跨域請(qǐng)求,我們就可以取回?cái)?shù)據(jù)。

          下面是在我們封裝完jsonp方法之后,向一個(gè)允許任何源向該服務(wù)器發(fā)送請(qǐng)求的網(wǎng)址xxx

          jsonp('https://matchweb.sports.qq.com/matchUnion/cateColumns?from=pc',?result?=>?{
          ??console.log(result);
          });

          CORS

          上文提到,不允許跨域的根本原因是因?yàn)?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #f48a00;">Access-Control-Allow-Origin已被禁止

          那么只要讓服務(wù)器端設(shè)置允許源就可以了

          原理:解決掉瀏覽器的默認(rèn)安全策略,在服務(wù)器端設(shè)置允許哪些源請(qǐng)求就可以了

          先來看一下下面的設(shè)置有哪些問題

          //?服務(wù)器端
          app.use((req,?res,?next)?=>?{
          ?//?*?允許所有源(不安全/不能攜帶資源憑證)
          ?res.header("Access-Control-Allow-Origin",?"*");
          ?res.header("Access-Control-Allow-Credentials",?true);

          ?/*?res.header("Access-Control-Allow-Headers",?"Content-Type,....");
          ?res.header("Access-Control-Allow-Methods",?"GET,...");?*/


          ?//?試探請(qǐng)求:在CORS跨域請(qǐng)求中,首先瀏覽器會(huì)自己發(fā)送一個(gè)試探請(qǐng)求,驗(yàn)證是否可以和服務(wù)器跨域通信,服務(wù)器返回200,則瀏覽器繼續(xù)發(fā)送真實(shí)的請(qǐng)求
          ?req.method?===?'OPTIONS'???res.send('CURRENT?SERVICES?SUPPORT?CROSS?DOMAIN?REQUESTS!')?:?next();
          });

          //?客戶端
          let?xhr?=?new?XMLHttpRequest;
          xhr.open('get',?'http://127.0.0.1:1001/list');
          xhr.setRequestHeader('Cookie',?'name=jason');
          xhr.withCredentials?=?true;
          xhr.onreadystatechange?=?()?=>?{
          ??if?(xhr.status?===?200?&&?xhr.readyState?===?4)?{
          ????console.log(xhr.responseText);
          ??}
          };
          xhr.send();

          當(dāng)我們一旦在服務(wù)器端設(shè)置了允許任何源可以請(qǐng)求之后,其實(shí)請(qǐng)求是不安全的,并且要求客戶端不能攜帶資源憑證(比如上文中的Cookie字段),瀏覽器端會(huì)報(bào)錯(cuò)。

          告訴我們Cookie字段是不安全的也不能被設(shè)置的,如果允許源為'*'的話也是不允許的。

          假如在我們的真實(shí)項(xiàng)目開發(fā)中

          正確寫法?

          • 設(shè)置單一源(安全/也可以攜帶資源憑證/只能是單一一個(gè)源)
          • 也可以動(dòng)態(tài)設(shè)置多個(gè)源:每一次請(qǐng)求都會(huì)走這個(gè)中間件,我們首先設(shè)置一個(gè)白名單,如果當(dāng)前客戶端請(qǐng)求的源在白名單中,我們把Allow-Origin動(dòng)態(tài)設(shè)置為當(dāng)前這個(gè)源
          app.use((req,?res,?next)?=>?{
          ??
          ??//?也可自定義白名單,檢驗(yàn)請(qǐng)求的源是否在白名單里,動(dòng)態(tài)設(shè)置
          ??/*?let?safeList?=?[
          ??"http://127.0.0.1:5500",
          ??xxx,
          ??xxxxx,
          ?];?*/

          ?res.header("Access-Control-Allow-Origin",?"http://127.0.0.1:5500");
          ?res.header("Access-Control-Allow-Credentials",?true);?//?設(shè)置是否可攜帶資源憑證

          ?/*?res.header("Access-Control-Allow-Headers",?"Content-Type,....");
          ?res.header("Access-Control-Allow-Methods",?"GET,...");?*/


          ?//?試探請(qǐng)求:在CORS跨域請(qǐng)求中,首先瀏覽器會(huì)自己發(fā)送一個(gè)試探請(qǐng)求,驗(yàn)證是否可以和服務(wù)器跨域通信,服務(wù)器返回200,則瀏覽器繼續(xù)發(fā)送真實(shí)的請(qǐng)求
          ?req.method?===?'OPTIONS'???res.send('CURRENT?SERVICES?SUPPORT?CROSS?DOMAIN?REQUESTS!')?:?next();
          });

          CORS的好處

          • 原理簡單,容易配置,允許攜帶資源憑證
          • 仍可以用 ajax作為資源請(qǐng)求的方式
          • 可以動(dòng)態(tài)設(shè)置多個(gè)源,通過判斷,將Allow-Origin設(shè)置為當(dāng)前源

          CORS的局限性

          • 只允許某一個(gè)源發(fā)起請(qǐng)求
          • 如多個(gè)源,還需要?jiǎng)討B(tài)判斷

          Proxy

          Proxy翻譯為“代理”,是由webpack配置的一個(gè)插件,叫"webpack-dev-server"(只能在開發(fā)環(huán)境中使用)

          Proxy在webpack中的配置

          const?path?=?require('path');
          const?HtmlWebpackPlugin?=?require('html-webpack-plugin');

          module.exports?=?{
          ??mode:?'production',
          ??entry:?'./src/main.js',
          ??output:?{...},
          ??devServer:?{
          ????port:?'3000',
          ????compress:?true,
          ????open:?true,
          ????hot:?true,
          ????proxy:?{
          ??????'/':?{
          ????????target:?'http://127.0.0.1:3001',
          ????????changeOrigin:?true
          ??????}
          ????}
          ??},
          ??//?配置WEBPACK的插件
          ??plugins:?[
          ????new?HtmlWebpackPlugin({
          ??????template:?`./public/index.html`,
          ??????filename:?`index.html`
          ????})
          ??]
          };

          圖解Proxy的原理

          Proxy代理其實(shí)相當(dāng)于由webpack-dev-server配置在本地創(chuàng)建了一個(gè)port=3000的服務(wù),利用node的中間層代理(分發(fā))解決了瀏覽器的同源策略限制。

          但是它只能在開發(fā)環(huán)境下使用,因?yàn)?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #f48a00;">dev-server只是一個(gè)webpack的一個(gè)插件;

          如果需要在生產(chǎn)環(huán)境下使用,需要我們配置Nginx反向代理服務(wù)器;

          另外如果是自己實(shí)現(xiàn)node服務(wù)層代理:無論是開發(fā)環(huán)境還是生產(chǎn)環(huán)境都可以處理(node中間層和客戶端是同源,中間層幫助我們向服務(wù)器請(qǐng)求數(shù)據(jù),再把數(shù)據(jù)返回給客戶端)

          Proxy的局限性

          只能在本地開發(fā)階段使用

          配置Nginx反向代理

          主要作為生產(chǎn)環(huán)境下跨域的解決方案。

          原理:利用Node中間層的分發(fā)機(jī)制,將請(qǐng)求的URL轉(zhuǎn)向服務(wù)器端的地址

          配置反向代理

          server?{
          ?listen:?80;
          ??server_name:?192.168.161.189;
          ??loaction:?{
          ??proxy_pass_http://127.0.0.1:1001;?//?請(qǐng)求轉(zhuǎn)向這個(gè)URL地址,服務(wù)器地址
          ????root?html;
          ????index?index.html;
          ??}
          }

          簡單寫了一下偽代碼,實(shí)際開發(fā)中根據(jù)需求來配。

          POST MESSAGE

          假設(shè)現(xiàn)在有兩個(gè)頁面,分別為A頁面port=1001、B頁面port=1002,實(shí)現(xiàn)頁面A與頁面B的頁面通信(跨域)

          原理:

          • 把 B頁面當(dāng)做A的子頁面嵌入到A頁面里,通過iframe.contentWindow.postMessage向B頁面?zhèn)鬟f某些信息
          • 在A頁面中通過window.onmessage獲取A頁面?zhèn)鬟f過來的信息ev.data(見下代碼)
          • 同理在B頁面中通過ev.source.postMessage向A頁面?zhèn)鬟f信息
          • 在A頁面中通過window.onmessage獲取B頁面?zhèn)鬟f的信息

          主要利用內(nèi)置的postMessageonmessage傳遞信息和接收信息。

          A.html

          //?把?B頁面當(dāng)做A的子頁面嵌入到A頁面里
          "iframe"?src="http://127.0.0.1:1002/B.html"?frameborder="0"?style="display:?none;">iframe>

          <script>
          ??iframe.onload?=?function?()?{
          ????iframe.contentWindow.postMessage('珠峰培訓(xùn)',?'http://127.0.0.1:1002/');
          ??}

          ??//=>監(jiān)聽B傳遞的信息
          ??window.onmessage?=?function?(ev)?{
          ????console.log(ev.data);
          ??}
          script>

          B.html

          
          
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    aanquye | 日韩高清国产一区在线 | 亚洲无码视频在线观看高清 | 超碰碰人人 | 日本久色 |