本文主要介绍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。