内容简介:跨域是前端工程中一个很常见的问题啦. 今天就来聊一聊跨域, 以及常见的处理方式和原理.跨域实际上源于浏览器对javascript的一种安全限制(也被称之为同源策略). 默认情况下, 我们只能访问同一协议、同一域名、同一端口下的资源. 如今都是提倡前后分离的情况下, 前端更是需要调用大量后台接口的场景, 因此解决跨域的问题就摆在了面前.产生跨域的原因我们主要归为这两点:
跨域是前端工程中一个很常见的问题啦. 今天就来聊一聊跨域, 以及常见的处理方式和原理.
什么是跨域?
跨域实际上源于浏览器对javascript的一种安全限制(也被称之为同源策略). 默认情况下, 我们只能访问同一协议、同一域名、同一端口下的资源. 如今都是提倡前后分离的情况下, 前端更是需要调用大量后台接口的场景, 因此解决跨域的问题就摆在了面前.
产生跨域的原因
产生跨域的原因我们主要归为这两点:
- 浏览器安全限制(不能读取不同域、端口、协议下的内容)
- 使用的是XHR(XMLHttpRequest)请求
同源策略这个原因是众所周知了, 它作为一个安全策略, 的确有效预防了某些安全上的问题. 但同时又阻止了大量适合使用的跨域请求. 而 XHR 也受同源策略影响: 浏览器不允许 javascript 查找跨域文档的内容. 使用 XHR, 文档内容都是通过 responseText
属性暴露, 因此同源策略不允许 XHR 进行跨域请求.
解决跨域
跨域很多情况下都需要后端的配合, 因此主要先来谈谈前端的跨域方案.
JSONP
前文我们说过, 产生跨域的原因之一XHR请求, 但是 script
发出的请求类型(type)并不是 xhr
, 因此可以解决跨域的问题.
JSONP 由回调函数和数据组成的, 实现方式就是动态创建一个 <script>
标签, 然后设置 src
属性指向的跨域的URL(包涵请求参数). 来向服务端请求数据.
比如我们要查询小明的信息, 这时我们得知它的 userID
为 1150, 同时我们都知道GET请求可以通过url进行传参, 因此我们向服务器发起请求:
var script = document.createElement('script') script.src = "https://www.example.com/users?user_id=1150" document.body.appendChild(script)
在插入 <script>
标签到 <body>
后, 浏览器立马就去请求服务器的资源. 值得注意的是, 使用 jsonp
也需要服务端的配合. 因此必须通过某种方式来告知服务端, 我们正在通过 <script>
标签调用请求, 必须返回一个JSONP响应, 而不应该是普通JSON响应.
至于什么叫 jsonp
响应呢? 这里其实很好理解.. 假设后端发回来的是 json
格式的数据, 我们也用不了呀, 数据还是数据, 不会做任何变化.. 为了让浏览器可以在 <script>
标签里直接使用, 我们需要让服务端返回一段js代码 —— 用函数包装的json的形式(这也jsonp中”P(padding)”的含义). 这个函数名前后端可约定. 如下:
// 服务端返回 js 代码到 <script>里 userData({'naem':'小明','id':1150,'level':'中等'}) // 前端定义函数 function userData(data){ // 当 jsonp 请求成功后, 将json传入函数并调用, 我们拿到 json 后就可以做一些其他的事 }
我们来拿B站为例. 打开chrome下的network, 上图就是 jsonp
的应用, 服务端返回的js脚本. 下图可以发现, 我们发出去的请求类型是 script
, 验证了前文所说的 <script>
不受同源策略影响的.
目前主流的类库都对 jsonp
进行了封装, 如 JQuery
的 getJSON
和 ajax
, 这里就不深入讲解了. 最后对 jsonp
总结一下:
jsonp
实际上是一个非正式传输协议, 或者说是一种”投机取巧”的方式. 我们可以利用 <script>
的特性从而进行数据交互解决跨域的问题. 相对来说, 它也有一定的局限性: 只能应用在GET请求上, 除此之外还有安全性的问题 —— 只能用在我们信任的服务端, 因为你不能保证对方未来会给你传些什么…
跨域资源共享(CORS)
概述
说完了”不正规”的 jsonp
, 紧接着我们再说说原生的 CORS
规范. 我们先来看看官方的定义:
CORS(Cross-origin resource Sharing, 跨资源共享), 定义了访问跨域资源时, 浏览器和服务器应该如何沟通. 其背后主要思想就是 使用自定义的HTTP头部 来让浏览器与服务器进行沟通, 从而决定请求或相应是否成功, 还是应该失败.
目前主流的浏览器都已经对CORS有着良好的支持, 而IE8 ~ 9则还需要使用专用的 XDomainRequest
这里我们抛开不谈.
这个功能实际上是由浏览器自动完成的, 我们并不需要做什么额外的工作. 对于开发者来说, 也就需要了解一些安全细节的问题, 这一点我们放在后面讲.
两种请求
浏览器发送CORS请求时, 会将请求分为 简单请求 与 非简单请求 .
在我们日常工作中, 常用的 简单请求 可以将其归为以下几点:
- 使用的方法(Methods)为
HEAD
、GET
、POST
- 请求头无自定义头
-
Content-Type
只能是以下几种- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
非简单请求:
-
PUT
,Delete
方法的 ajax 请求 - 发送 JSON 格式的 ajax 请求(比如post数据)
- 带自定义头的 ajax 请求
如果是简单请求, 则会 先执行, 后判断 。执行的过程大致如下:
浏览器发起请求检测到是 CORS 请求, 然后添加一个 origin
字段(其中包含页面源信息: 协议、域名、端口) => 服务端收到后作相应的处理(对比 origin
, 服务端判断这个源是否接受)返回结果给浏览器 => 浏览器检查响应头是否允许跨域信息 => 允许, 那就当做没事发生. 不允许, 浏览器抛出相应的错误信息(值得一提的是, 这时状态码也还有可能是200).
非简单请求执行顺序又有些不同. 在发生 CORS 请求时, 浏览器预先发送一个 option
请求. 浏览器这种行为被称之为 预检请求(Preflighted request) . 其中包含如下的请求头:
- origin: 同上,包含页面源信息.
- Access-Control-Request-Methods: 请求方法
- Access-Control-Request-Header: 自定义头部信息, 多个头部以逗号分隔(可选, 看请求时有没有定义请求头)
举个栗子, 我们用JQuery发送一段JSON格式的请求做演示:
var result; $.ajax({ type : "post", url: "https://www.example/api/rank", contentType : "application/json;charset=utf-8", data: JSON.stringify({name: "something"}), success: function(json){ result = json; } });
这时请求头(Request Headers)信息如下:
Accept: */* Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Access-Control-Request-Header: content-type Access-Control-Request-Methods: POST Connection:keep-alive Host: https://www.example.com origin: localhost:8080 User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1
服务端接收到预检请求后, 判断是否允许这种类型的请求. 在响应头(Response Header)上返回如下头部与浏览器进行交流:
- Access-Control-Allow-Origin: 服务端允许的源信息
- Access-Control-Allow-Methods: 服务端允许的方法, 多个方法可以使用顿号分隔
- Access-Control-Allow-Headrs: 服务端允许的头部, 多个头部可以使用顿号分隔
当预检请求被通过后, 我们原本想要发送的请求才会发送出去.
另外, 细心的你或许已经注意到了, 非简单请求这一来一回需要发送两次请求, 如果频率高的情况下岂不是很费性能又影响效率? 所幸的是HTTP协议新增(IE10+)了一个响应头用于缓存预检请求. 服务端在响应头添加如下字段:
Access-Control-Max-Age: 3600
这个响应头表示这个预检请求可以缓存多长时间, 单位为秒. 这里3600s = 1h, 也就是说一个小时内可以不用再发预检命令了.
带 cookie 的跨域请求
默认情况下, 跨域请求是不带上 cookie 的. 前端需要将 withCredentials
属性设置为 true
, 同时还需要服务端设置 Access-Control-Allow-Credentials
为 true
启动 cookie. 如果在发送 cookie 的时候, 浏览器检测到服务端响应头没有这个头部, 那么就会在控制台抛出一个错误.
另外, 还有一个值得注意的是. 服务端响应头设置了 Access-Control-Allow-Origin: *
的话, 是不能满足带 cookie 的跨域请求的. 因此有这种场景不能使用通配符, 需要全匹配字段.
CORS 总结
简单总结一下 CORS. CORS的出现也是为了解决跨域的问题. 只不过和 JSONP
不同, 它是纳入规范的一部分, 它几乎支持所有的类型的HTTP请求(JSONP只能使用GET). 唯一美中不足的也就是兼容性的问题, 因此可以使用JSONP作向下的兼容
事实上前端在 CORS 上并没有多少可操作的余地, 主要的还是浏览器来处理、服务端在设置, 但是并不代表我们就不需要了解这些知识啦.
嗯, 其他的跨域方法先挖个坑..
以上所述就是小编给大家介绍的《聊聊常见的跨域问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Once Upon an Algorithm
Martin Erwig / MIT Press / 2017-9-8 / GBP 22.95
How Hansel and Gretel, Sherlock Holmes, the movie Groundhog Day, Harry Potter, and other familiar stories illustrate the concepts of computing. Picture a computer scientist, staring at a screen and......一起来看看 《Once Upon an Algorithm》 这本书的介绍吧!