本文主要介紹JSONP、CORS兩種跨域方式,後臺採用Koa模擬,真正的目標是理解整個跨域的流程。至於什麼是跨域和瀏覽器同源策略的問題,請同窗們自行百度。javascript
JSONP 實際上是一種trick, 利用瀏覽器對帶有src標籤的能力實現訪問跨域數據的小技巧(像img、link標籤等不存在跨域問題)。css
<!DOCTYPE html> <html> <head> <title>模擬JSONP跨域請求</title> </head> <body> <script type="text/javascript"> var message = 'hello world'; function doSomething(data) { // 處理返回的數據 document.write(data); } </script> <script src="http://127.0.0.1:3000/jsonp?callback=doSomething&msg=message"></script> </body> </html>
var Koa = require('koa'); var Router = require('koa-router'); var app = new Koa(); var router = new Router(); router.get('/', (ctx, next) => { ctx.body = 'Hello World!'; }); // jsonp跨域請求 router.get('/jsonp', (ctx, next) => { // 獲取參數 const query = ctx.request.query; ctx.body = `${query.callback}(${query.msg})` }) app .use(router.routes()) .use(router.allowedMethods()); app.listen(3000);
當後端的請求完成以後,會回調callback函數,並傳入相應的message參數,執行doSomething函數。html
優勢:兼容性好java
缺點:ios
MDN:跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。git
須要注意的是,針對CORS,異步請求會被分爲簡單請求和非簡單請求,非簡單請求會先發起一次preflight,也就是咱們所說的預檢。github
使用下列方法之一:json
HTTP請求頭僅限於如下:axios
Content-Type的值僅限於下列三者之一:後端
看上去十分複雜,咱們怎麼來理解?其實簡單請求就是HTML form原生表單不依賴腳本能夠發出的請求,咱們來看一下表單的enctype屬性:
其實簡單請求還能夠分爲原生form請求(不依賴腳本)和經過腳本發起的簡單請求,咱們先來看一下原生的form請求:
<!DOCTYPE html> <html> <head> <title>CORS-form</title> </head> <body> <form action="http://127.0.0.1:3000/cors/form-request" method="get" class="form-example"> <div class="form-example"> <label for="name">Enter your name: </label> <input type="text" name="name" id="name"> </div> <div class="form-example"> <label for="email">Enter your email: </label> <input type="email" name="email" id="email"> </div> <div class="form-example"> <input type="submit" value="Subscribe!"> </div> </form> </body> </html>
var Koa = require('koa'); var Router = require('koa-router'); var app = new Koa(); var router = new Router(); router.get('/', (ctx, next) => { ctx.body = 'Hello World!'; }); // CORS原生表單請求 router.get('/cors/form-request', (ctx, next) => { ctx.body = "Hello easy form!"; }); app .use(router.routes()) .use(router.allowedMethods()); app.listen(3000);
咱們看到原生表單不存在跨域的狀況,咱們再來看下用腳原本模擬表單提交:
<!DOCTYPE html> <html> <head> <title>cors</title> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> </head> <body> <script> function easyRequest() { axios({ method: 'get', url: 'http://127.0.0.1:3000/cors/form-request', params: { username: 'test', email: "test.com" }, headers: { 'Content-type': 'application/x-www-form-urlencoded' } }).then((res)=> { document.write(res.data) }) } easyRequest(); </script> </body> </html>
咱們會發現Request Headers頭裏面添加了Origin標籤。Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器會根據這個值,決定是否贊成此次請求。 若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。(注意,這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。)
下面咱們再來看一下非簡單請求:
<!DOCTYPE html> <html> <head> <title>cors</title> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> </head> <body> <script> function request() { axios({ method: 'put', url: 'http://127.0.0.1:3000/cors/request', params: { msg: 'hello cors' }, headers: { 'Content-type': 'application/x-www-form-urlencoded' } }).then((res)=> { document.write(res.data) }) } setInterval(request, 5000); </script> </body> </html>
var Koa = require('koa'); var Router = require('koa-router'); var app = new Koa(); var router = new Router(); // 設置CORS app.use(async (ctx, next) => { ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Methods', 'GET,POST,PUT'); ctx.set('Access-Control-Allow-Headers', 'x-requested-with, Content-Type'); ctx.set('Access-Control-Max-Age', 10); if (ctx.request.method == 'OPTIONS') { ctx.body = 200; } else { await next(); } }); // CORS跨域非簡單請求 router.put('/cors/request', (ctx, next) => { ctx.body = "Hello world!"; }); app .use(router.routes()) .use(router.allowedMethods()); app.listen(3000);
咱們會發現多了一次OPTIONS請求,這個就是咱們所說的預檢請求。瀏覽器會詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP動詞和頭信息字段。只有獲得了確定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,不然就報錯。
若是Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。
一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的CORS請求,就都跟簡單請求同樣,會有一個Origin頭信息字段。咱們還能夠經過設置Access-Control-Max-Age來控制"預檢"請求的時效性。
至此跨域的實踐就所有結束了,咱們思考一下瀏覽器爲何要區分簡單請求和非簡單請求呢?
咱們來看一下賀師俊老師是怎麼解釋的:
預檢這種機制只能限於非簡單請求。在處理簡單請求的時候,若是服務器不打算接受跨源請求,不能依賴 CORS-preflight 機制。由於不經過 CORS,普通表單也能發起簡單請求,因此默認禁止跨源是作不到的。既然如此,簡單請求發 preflight 就沒有意義了,就算髮了服務器也省不了後續每次的計算,反而在一開始多了一次 preflight。
有些人把簡單請求不須要 preflight 理解爲『向下兼容』。這也不能說錯。但嚴格來講,並非『爲了向下兼容』而不能發。理論上瀏覽器能夠區別對待表單請求和非表單請求 —— 對傳統的跨源表單提交不發 preflight,從而保持兼容,只對非表單跨源請求發 preflight。
但這樣作並無什麼好處,反而把事情搞複雜了。好比原本你能夠直接用腳本發跨源普通請求,儘管(在服務器默認沒有跨源處理的狀況下)你沒法獲得響應結果,可是你的需求可能只是發送無需返回,好比打個日誌。但如今若是服務器不理解 preflight 你就幹不了這個事情了。
並且若是真的這樣作,服務器就變成了默認容許跨源表單,若是想控制跨源,仍是得(跟本來同樣)直接在響應處理中執行跨源計算邏輯;另外一方面服務器又須要增長對 preflight 請求的響應支持,執行相似的跨源計算邏輯以控制來自非表單的相同跨源請求。服務器一般沒有區分表單/非表單差別的需求,這樣搞純粹是折騰服務器端工程師。
因此簡單請求不發 preflight 不是由於不能兼容,而是由於兼容的前提下發 preflight 對絕大多數服務器應用來講沒有意義,反而把問題搞複雜了。
https://juejin.im/post/684490...
http://www.ruanyifeng.com/blo...
https://github.com/warplan/JS...
碼字實屬不易,但願你們能關注一波公衆號,一塊兒學習,一塊兒Easy。