博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
前端跨域真Easy,妈妈再也不用担心了
阅读量:2198 次
发布时间:2019-05-03

本文共 4227 字,大约阅读时间需要 14 分钟。

本文主要介绍 JSONP、CORS 两种跨域方式,后台采用 Koa 模拟,真正的目标是理解整个跨域的流程。 至于什么是跨域和浏览器同源策略的问题,请同学们自行百度。

JSONP

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

  模拟JSONP跨域请求    
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 的 [ ] 元素时使用此值。
  • text/plain

其实简单请求还可以分为原生 form 请求(不依赖脚本)和 通过脚本发起的简单请求,我们先来看一下原生的 form 请求:

      CORS-form        
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);

在这里插入图片描述

我们看到原生表单不存在跨域的情况,我们再来看下用脚本来模拟表单提交:

      cors              

在这里插入图片描述

我们会发现 Request Headers 头里面添加了 Origin 标签。Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器会根据这个值,决定是否同意这次请求。 如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。(注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是 200。)

非简单请求

下面我们再来看一下非简单请求:

  cors    
var Koa = require("koa");var Router = require("koa-router");var app = new Koa();var router = new Router();// 设置CORSapp.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 对绝大多数服务器应用来说没有意义,反而把问题搞复杂了。

参考文章

源码地址

欢迎关注

码字实属不易,希望大家能加一下,一起学习,一起Easy。

在这里插入图片描述

在这里插入图片描述

转载地址:http://tuqub.baihongyu.com/

你可能感兴趣的文章
深度学习的主要应用举例
查看>>
word2vec 模型思想和代码实现
查看>>
怎样做情感分析
查看>>
用深度神经网络处理NER命名实体识别问题
查看>>
用 RNN 训练语言模型生成文本
查看>>
RNN与机器翻译
查看>>
用 Recursive Neural Networks 得到分析树
查看>>
RNN的高级应用
查看>>
TensorFlow-7-TensorBoard Embedding可视化
查看>>
轻松看懂机器学习十大常用算法
查看>>
一个框架解决几乎所有机器学习问题
查看>>
特征工程怎么做
查看>>
机器学习算法应用中常用技巧-1
查看>>
机器学习算法应用中常用技巧-2
查看>>
通过一个kaggle实例学习解决机器学习问题
查看>>
决策树的python实现
查看>>
Sklearn 快速入门
查看>>
了解 Sklearn 的数据集
查看>>
用ARIMA模型做需求预测
查看>>
推荐系统
查看>>