在工做中,不免會遇到跨域的問題,就像你高高興興的帶着老婆吃着火鍋,啊不對,是匆匆忙忙的在搬磚,忽然瀏覽器告訴你跨域了,意不意外?javascript
既然遇到了,就只能解決他,平時一頓亂操做,也能解決問題,但一直沒有好好的來總結。html
俗話說的好,知己知彼、百戰不殆。咱們來看看什麼是跨域?誰在搞事情?前端
原來是瀏覽器在搞事情,不過,瀏覽器表示它不想背這個鍋:java
同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。node
這是官方的解釋,比較拗口,意思就是,爲了網頁更加安全而設計的安全策略,這個同源策略規定:json
若是兩個頁面的協議,端口(若是有指定)和域名都相同,則兩個頁面具備相同的源,訪問不受限制;不然則爲跨源(跨域),限制訪問。後端
瀏覽器爲何要搞個同源策略呢?api
這是爲了防止CSRF攻擊(Cross-site request forgery),中文名稱:跨站請求僞造。誰不怕坐着火車出了城,忽然就被麻匪給劫了不是。跨域
既然跨域必須存在,而咱們又必須繞不過它,那咱們該如何解決呢?瀏覽器
一、JSONP-----須要後端接口配合
利用script、img這樣沒有跨域限制的標籤,來發起請求。
初版的JSONP
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 </head> 6 <body> 7 <script type='text/javascript'> 8 // 後端返回直接執行的方法,至關於執行這個方法,因爲後端把返回的數據放在方法的參數裏,因此這裏能拿到res。 9 window.jsonpCb = function (res) { 10 console.log(res) 11 } 12 </script> 13 <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script> 14 </body> 15 </html>
封裝版:
1 /** 2 * JSONP請求工具 3 * @param url 請求的地址 4 * @param data 請求的參數 5 * @returns {Promise<any>} 6 */ 7 const request = ({url, data}) => { 8 return new Promise((resolve, reject) => { 9 // 處理傳參成xx=yy&aa=bb的形式 10 const handleData = (data) => { 11 const keys = Object.keys(data) 12 const keysLen = keys.length 13 return keys.reduce((pre, cur, index) => { 14 const value = data[cur] 15 const flag = index !== keysLen - 1 ? '&' : '' 16 return `${pre}${cur}=${value}${flag}` 17 }, '') 18 } 19 // 動態建立script標籤 20 const script = document.createElement('script') 21 // 接口返回的數據獲取 22 window.jsonpCb = (res) => { 23 document.body.removeChild(script) 24 delete window.jsonpCb 25 resolve(res) 26 } 27 script.src = `${url}?${handleData(data)}&cb=jsonpCb` 28 document.body.appendChild(script) 29 }) 30 } 31 // 使用方式 32 request({ 33 url: 'http://localhost:9871/api/jsonp', 34 data: { 35 // 傳參 36 msg: 'helloJsonp' 37 } 38 }).then(res => { 39 console.log(res) 40 })
二、空iframe和form配合
由於script標籤加載資源的方式就是GET。因此JSONP只能發GET請求,那麼咱們發送POST請求這麼辦呢?
1 const requestPost = ({url, data}) => { 2 // 首先建立一個用來發送數據的iframe. 3 const iframe = document.createElement('iframe') 4 iframe.name = 'iframePost' 5 iframe.style.display = 'none' 6 document.body.appendChild(iframe) 7 const form = document.createElement('form') 8 const node = document.createElement('input') 9 // 註冊iframe的load事件處理程序,若是你須要在響應返回時執行一些操做的話. 10 iframe.addEventListener('load', function () { 11 console.log('post success') 12 }) 13 14 form.action = url 15 // 在指定的iframe中執行form 16 form.target = iframe.name 17 form.method = 'post' 18 for (let name in data) { 19 node.name = name 20 node.value = data[name].toString() 21 form.appendChild(node.cloneNode()) 22 } 23 // 表單元素須要添加到主文檔中. 24 form.style.display = 'none' 25 document.body.appendChild(form) 26 form.submit() 27 28 // 表單提交後,就能夠刪除這個表單,不影響下次的數據發送. 29 document.body.removeChild(form) 30 } 31 // 使用方式 32 requestPost({ 33 url: 'http://localhost:9871/api/iframePost', 34 data: { 35 msg: 'helloIframePost' 36 } 37 })
三、CORS
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)跨域資源共享 CORS 詳解。看名字就知道這是處理跨域問題的標準作法。
CORS須要瀏覽器和服務器同時支持,服務器需作以下設置:
3.一、Access-Control-Allow-Origin 該字段必填。它的值要麼是請求時Origin字段的具體值,要麼是一個*,表示接受任意域名的請求。例如:
ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')
3.二、Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值,表示是否容許發送Cookie.默認狀況下,不發生Cookie,即:false。對服務器有特殊要求的請求,好比請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json,這個值只能設爲true。若是服務器不要瀏覽器發送Cookie,刪除該字段便可。例如
ctx.set('Access-Control-Allow-Credentials', true)
3.三、Access-Control-Allow-Methods 該字段必填。它的值是逗號分隔的一個具體的字符串或者*,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。例如:
ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS')
3.四、Access-Control-Expose-Headers 該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。例如:
ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t')
3.五、Access-Control-Max-Age
該字段可選,用來指定本次預檢請求的有效期,單位爲秒。在有效期間,不用發出另外一條預檢請求。
前端:
fetch(`http://localhost:9871/api/cors?msg=helloCors`, { // 須要帶上cookie credentials: 'include', // 這裏添加額外的headers來觸發非簡單請求 headers: { 't': 'extra headers' } }).then(res => { console.log(res) })
四、代理
咱們使用Nginx做爲咱們的代理服務器。Nginx配置
server{ # 監聽9099端口 listen 9099; # 域名是localhost server_name localhost; #凡是localhost:9099/api這個樣子的,都轉發到真正的服務端地址http://localhost:9871 location ^~ /api { proxy_pass http://localhost:9871; } }
Nginx配置好以後,前端什麼也不用幹,正常請求就行,Nginx會把請求轉發到真正的地址。
如今,跨域對你來講,應該不是事兒了吧,能夠開開心心的吃火鍋了。