乐趣区

关于javascript:前端跨域真Easy妈妈再也不用担心了

本文次要介绍 JSONP、CORS 两种跨域形式,后盾采纳 Koa 模仿,真正的指标是了解整个跨域的流程。

至于什么是跨域和浏览器同源策略的问题,请同学们自行百度。

JSONP

JSONP 其实是一种 trick, 利用浏览器对带有 src 标签的能力实现拜访跨域数据的小技巧(像 img、link 标签等不存在跨域问题)。

前端代码实现:


<!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 函数。

JSONP 的优缺点

长处:兼容性好

毛病:

  • JSONP 只反对 GET 申请
  • XMLHttpRequest 绝对于 JSONP 有着更好的错误处理机制

CORS

MDN:跨域资源共享 (CORS) 是一种机制,它应用额定的 HTTP 头来通知浏览器 让运行在一个 origin (domain) 上的 Web 利用被准许拜访来自不同源服务器上的指定的资源。当一个资源从与该资源自身所在的服务器不同的域、协定或端口申请一个资源时,资源会发动一个跨域 HTTP 申请。

须要留神的是,针对 CORS,异步申请会被分为简略申请和非简略申请,非简略申请会先发动一次 preflight,也就是咱们所说的预检。

简略申请

应用下列办法之一:

  • GET
  • HEAD
  • POST

HTTP 申请头仅限于以下:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

看上去十分复杂,咱们怎么来了解?其实简略申请就是 HTML form 原生表单不依赖脚本能够收回的申请,咱们来看一下表单的 enctype 属性:

enctype

  • application/x-www-form-urlencoded:未指定属性时的默认值。
  • multipart/form-data:当表单蕴含 type=file 的 input 元素时应用此值。
  • text/plain

其实简略申请还能够分为原生 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) => {console.log("form request");

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 指定的域名在许可范畴内,服务器返回的响应,会多出几个头信息字段。

  • Access-Control-Allow-Headers: 首部字段用于预检申请的响应。其指明了理论申请中容许携带的首部字段。
  • Access-Control-Allow-Methods: 首部字段用于预检申请的响应。其指明了理论申请所容许应用的 HTTP 办法。
  • Access-Control-Allow-Origin: 参数的值指定了容许拜访该资源的外域 URI

一旦服务器通过了 ” 预检 ” 申请,当前每次浏览器失常的 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。

退出移动版