什么是跨域?
首先,现代浏览器为了安全,做了一个同源限制.
也就是所谓的同源安全策略
.
本质上,其实是不存在所谓的跨不跨域的.
把浏览器想象成一个发送网络请求的软件.
按照道理来说,请求都是可以发送出去的.
但是在 web 端,浏览器里,这么做的就不合适.
如果网络上的接口可以不受限制的被任意人调用.
那将是一个非常混乱的场景.
所以,为了防止这种情况,浏览器做了这个同源策略来防止这种情况发生.
对,一般服务器不管这个事情.
跨域指的是,A
网站内部向B
网站发送一个AJAX
请求.
由于浏览器有同源策略的限制,默认情况下,是不允许 A
网站向 B
网站请求数据资源的.
http://my.website.com/ ---> http://your.website.com/ 是不允许的.
具体来说,凡是发送请求中,协议
,主机名
,端口号
有一个不同,即为跨域请求.
上述例子:
my.website.com
和your.website.com
就属于主机名
不同而导致的跨域.
具体可以参照下面这个表.
总结一句:
协议相同
+ 域名相同
+ 端口号相同
浏览器才认为是同一个网站.
才不同受到同源策略的影响.
才可以正常的发送 AJAX 请求.
其他情况下,发送 AJAX 请求,都属于跨域.
请注意: 这里说的是
XMLHttpRequest
下的AJAX
请求.
对于
,
,
等标签,就不存在跨域请求.(除非对方后台做了防盗链)
继续补充:
所谓的同源策略是浏览器实现的,而和后台服务器无关.
A
发送请求到B
. 请求实际上是发送到了B
后台,B
后台接受到数据后.
其实也有返回.
只不过,这个数据返回到浏览器之后,浏览器把这个数据给劫持了.不让A
网站使用.
为什么要跨域?
既然跨域这么麻烦,都不能从网站后台拿到数据,那为什么还要搞跨域呢?
因为当一个项目变大时,把所有的内容都丢在一个网站或者是后台服务器中是不现实的.
比如:
一个网站体量很大.有很多可以独立且复杂的业务
- 比如有一个订单管理的后台数据API网站服务.
- 比如有一个用户管理的后台数据API网站服务.
- 比如有一个新闻管理的后台数据API网站服务.
最后剩下的就是web
的UI层面的东西.
把这个UI层面的东西和哪个数据服务API的网站集成在一起比较合适呢?
都不适合.
它应该是一个专门的网站.
所以域名可能存在如下情况:
- 主网站(UIWeb)域名为:
http://www.relax.com/
- 订单数据服务接口域名为:
https://order.relax.com/orderList
- 用户数据服务接口域名为:
https://users.relax.com/userList
- 新闻数据服务就扣域名为:
http://news.relax.com/newList
所以,UIWeb 如果需要请求这数据.
- 请求订单数据: 协议不同
https|http
, 二级域名不同order | www
, 端口号不同443|80
--> 跨域 - 请求用户数据: 协议不同
https|http
, 二级域名不同users | www
, 端口号不同443|80
--> 跨域 - 请求新闻数据列表: 协议相同
http|http
, 二级域名不同order | www
, 端口号相同80|80
--> 跨域
不管请求哪个后台服务器的数据,都存在跨域的情况.
也就是无法利用 ajax
异步的获取到数据内容.
利用 jsonp 进行跨域
jsonp 跨域是里用在浏览器中,下载一个script标签是不受同源策略限制的特性.(bootcdn中不是有很多的js文件下载吗?)
具体实现原理大概分下面几步
- 客户端动态的创建一个
script
标签 - 设置
script
标签的src
为跨域的服务器后台. -
src
中需要带一个callback
查询字符串,告诉后台,前端提供的方法名是什么. - 然后后端,直接返回一一串
callback(data)
的字符串给浏览器即可.
前端端口是:8080
button.addEventListener('click', function () {
// 利用下载script标签是不存在浏览器同源策略的特点.
const scriptTag = document.createElement('script')
scriptTag.type = 'text/javascript'
// 第一 22222 端口号不同.
// 第二 callback=show 是传递给22222后台服务器的查询字符串参数.
// 第三 后台服务器根据拿到了这个callback = show,往前端输出一些文本流.(这里就是和利用下载script标签一模一样.)
scriptTag.src = 'http://localhost:12345/getUserinfo?callback=show'
document.body.appendChild(scriptTag)
},false)
// 这里的show 方式就是上述 jsonp 中的 callback 查询字符串指定的值.
function show (user) {
let dataStr = ""
for(let prop in user) {
dataStr += prop + ":" + user[prop] + "
"
}
unOriginPlaceHolder.innerHTML = dataStr
}
后端端口是:12345
const app = require('express')()
const url = require('url')
const queryString = require('querystring')
app.get('/getUserInfo', (request, response) => {
const queryObj = url.parse(request.url, true).query
const callback = queryObj.callback // 获取callback参数
const data = {
userid: 1,
name: '李四',
age: 22,
profession: '软件工程师'
}
const objStingIfy = JSON.stringify(data)
console.log(objStingIfy)
// 拼接 callback(数据) 字符串,返回给前端
response.end(`${callback}(${objStingIfy})`)
})
app.listen(22222, () => {
console.log('服务启动成功!')
})
结果:
jsonp使用总结:
- 利用
script
标签异步加载,而非传统的ajax
- 异步
script
加载不受浏览器同源策略限制. - 给
script.src=callback=fn
来告知请求的后台前端提供的fn
是什么 - 基本就是前端提供方法,后端提供数据并调用前端的这个方法.
-
jsonp
只支持get
请求.(本质上,不就是下载文件吗?下载文件一般都是get请求)
cors
cors
是 Cross-Origin-Resource-Sharing
的缩写.
也是现代浏览器普遍支持的一种非常方便的跨域方案.
它的具体工作流程是:
浏览器在检测到你发送的 ajax
请求不是同源请求时,会自动在 http 的头部添加一个 origin
字段.
之前也说过,请求怎么滴都能发送出去.
我们拿不到数据是因为浏览器在中间做了一层劫持.
获取不到数据的原因也很简单:
- 这是一次跨域请求.
- 请求确实发送到服务器了.
- 服务器也把数据返回到了浏览器.
- 但是服务器返回的响应头里,没有告诉浏览器哪个域名可以访问这些数据(也就是没有设置 Access-Control-Allow-Origin)
- 于是浏览器就把这个数据丢弃了.我们也就无法获取到这个数据.
这个时候,只需要后台在相应头里加上一个 Access-Control-Allow-Origin
:*
即可完成跨域数据获取.
CORS 跨域方案简单总结:
- 当浏览器发送一个请求是跨域的时候,会自动加上
Origin
- 需要后台配合设置响应头
Access-Control-Allow-Origin:*|Origin
即可完成跨域. - CORS 支持
GET
,POST
常规请求. - 同时也支持
PUT
,DELETE
等非post
,get
的请求,但会先发一次预检请求.
还有一种proxy模式.
核心思想是:
让前端请求我们自己的后台,让后台去跨域请求真是的数据,然后把数据返回给前台.
实现方式,可以利用 nginx
做反向代理.以及我们自己写一个后台中转的服务器.
码云DEMO地址