跨域原来如此!

486 阅读7分钟

第一部分:什么是跨域?

首先,说一下同源,如果两个 url 的协议、域名、端口三者完全相同,就称之为同源。

同源之间获取资源是不受限制的,如果不满足同源(也就是上面三个至少有一个不同),那么获取资源就会受限,一般我们称之为 跨域。

举个栗子: 比如现在有一个 url 是 http://localhost:2300

另一个地址是 file:///Users/workspace/crossDomainDemo/index.html

注意看,这两个 url 的协议不一样,所以如果 index.html 向 localhost:2300 发送请求的话,就会被拦截下来。 当然,不是所有请求都会被拦截,但是大部分都会被拦截。

【Q&A】

Q:拦截是显示undefined还是直接报错?

A:会报一个类似这样的错误,如下图

第二部分:如何解决跨域?

那要如何解决这个问题(跨域)呢?

有很多个方案, 在此说一个现在常用的和一个古代常用的方案。

1.现在常用的方案是 CORS

cors 说的是一种机制,其实相当于一个约定,就是用 http 头部字段来开一扇后门。

服务器本来收不到浏览器发的请求(因为被拦截了),现在服务器说我可以给你开一扇门,但 是你要符合一定的条件。

请求如下图。浏览器会在原来的请求头部上新增一个字段 Origin,用来表示是从哪里发的,这 个值一般是一个网址,我这里是本地文件,所以值是 null。

响应如下图。然后服务器响应会返回一个字段 Access-Control-Allow-Origin,表示能放行什么网址的请求,* 表示放行所有请求。 浏览器本来会拦截跨域请求,但是加了 cors 这样的相关字段之后,浏览器就不会拦截

【Q&A】

Q:cors是直接往网址上加吗?

Q:如何设置 cors?

A:

Q:网址发送请求后如果被屏蔽才会响应cors机制吗?

A:是浏览器发送的时候就会带上,如果服务器没有告诉浏览器可以放行,那么浏览器就会报错。

小结: 浏览器发请求的时候在头部字段加一个 Origin 用来表示自己从哪里来。服务器收到请求返回响应的时候会在头部字段加一个 Access-Control-Allow-Origin 用来表示 能接收从哪里来的请求。如果两个匹配上了,就继续。如果没有匹配上,浏览器就会报错。 (相关文档 developer.mozilla.org/zh-CN/docs/…

浏览器会根据情况发送简单请求或者非简单请求。

上面的链接就是说的简单请求,这种情况比较简单,就是浏览器发一个请求,然后服务器返回 一个响应。

那么,非简单请求呢,比如前端与后端一般用 json 格式的数据进行通信,前端发送的请求头部字 段会带有 Content-Type,并且值为 application/json,这种就是非简单请求。(相关文档 developer.mozilla.org/zh-CN/docs/…

当然 mdn 称之为 预检请求

【Q&A】

Q:这两个匹配是一一对应的关系吗?就是说,是不是具有唯一性?

A:包含关系。浏览器发送的请求 Origin 一定有一个确定的值,这个值如果被 Access-Control-Allow- Origin 包含, 那么就可以放行,不然不放行。

预检请求说的是浏览器先发一个请求试探一下,如果服务器说放行,然后浏览器根据情况继续。 预检请求会先发送一个 OPTIONS 方法(注意,平时一般用 GET 方法或者 post 方法),并且 会在请求头部加上 Origin 和 Access-Control-Request-Headers 还有 Access-Control- Request-Method

服务器如果能够正确处理这个OPTIONS请求,浏览器才会把后面的请求发出来,如果没有正确 处理,浏览器依然报错

那么如何正确处理呢?

请求头部用了 content-type,那么服务器就需要允许这个头部,也就是 Access-Control- Allow-Headers: Content-Type

method 也是一样的道理,不过因为例子里的 method 是 GET,所以可以省略,其他要写上

正确处理之后,浏览器就会发出真正的请求 否则就是会当场报错

【总结一下】

cors 说的是浏览器在发送请求的时候会带上一些特殊头部字段,然后服务器检查一下,如果能 放行就会放行,如果不能放行就会报错。

放行的话又会分为两种情况,简单请求和预检请求。

简单请求很简单, 不用说。

预检请求,会先发送 OPTIONS 方法问服务器,服务器除了检查 Origin 还会检查 headers 和 method,都符合要求浏览器才会发出真正的请求,否则会报错。

2.古代方案:JSONP

之前说跨域问题,如果服务器不能放行的话,浏览器会报错

但是对于 script 标签来说,有一个折中方案,也就是 src 的值可以不受服务器限制

比如 index.html 里面的 script 标签,然后可以访问 /api/todo/all/jsonp 这种 url,浏览器会自 动以 GET 方法发送这样的请求

这个请求的响应是一个字符串,而且这个字符串很奇怪,是 add(1, 2)

浏览器收到响应之后会把这个字符串当成代码,也就是会执行这个代码

那么,如果我们提前定义了 add 这个函数,并且接收两个参数,实现逻辑,add(1, 2) 就能能够 调用成功的

比如我提前定义好 add 函数,那么调用 add(1, 2) 就会成功

jsonp 就是这样的。

【Q&A】

Q:jsonp的作用类似于“把函数提前定义好了,后面直接调用”,这样理解对吗?

A:jsonp 的作用是返回和前端约定好的内容,这样前端就可以直接用

Q:那我后端返回 function(arg1, arg2)这种字符串时,怎么知道前端有没有对应的方法?

A:比如目前后端返回的是 add(1, 2),那如果前端定义的函数不是这个名字,不就用不了了吗, 扩展性太差了,所以要改一下

怎么改呢?

发送 get 请求的时候把参数放在 url 的 query string 里面,比如 callback 就是函数名称, param1 和 param2 就分别是两个参数

后端解析出 query string 里面的这些内容,然后把这些内容拼接成函数调用的形式

现在返回的是 add1 这个函数,并且这个函数是前端决定的。request.query 获取的就是 query string 对应的字典。

这样的话,前端调用什么函数,传入什么参数,都是由前端自己实现的,后端只是一个中转站, 什么都不用做,只是拼接需要格式的数据

实际上就有点类似 callback,后端只负责调用 callback,但是 callback 具体是什么函数,由前端 决定

特别提醒 】最后说一句,跨域问题一定是要后端解决的,要么用后端的能力(比如 node nginx 等) 如果一个人说前端能解决跨域,那么这个人 1,不懂跨域 2,不懂前端

【Q&A】

Q:因为是在script标签中发生的事情,所以在页面加载的时候等于就调用请求,然后后端返回对 应的方法,然后前端再执行。等于整个动作是在 页面加载的时候完成,这么理解可以吗?

A:差不多吧,但实际上可以用 js 控制 script 标签的生成的 所以也可以不在页面加载的时候执行

Q:script标签插入页面时就会被执行吗?还是得刷新才执行。

A:插入就会执行的, script 标签插入到 dom 树的时候,就会自动用 get 方法往 src 对应的 url 发送请求

the end ! 恭喜看到此处的小可爱, 想必关于跨域问题, 现在你已经完全弄清楚啦~ 进阶大成功!