请求的几种方式
get:(select)从服务器请求数据,(给的少拿的多)
post:(create)向服务器发送数据,(给的多拿的少)
put:向服务器存放一些内容
delete:删除服务器的一些内容
head:只请求页面的头部
trace:回显服务器收到的请求,主要用于测试或诊断
options:允许客户端查看服务器的性能
connect:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
什么是同源策略
- 同源策略 必须是同协议、同域名、同端口
什么是跨域
当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了
我们来看下面的页面是否与 http://store.company.com/dir/index.html 是同源的?
http://store.company.com/dir/index2.html 同源
http://store.company.com/dir2/index3.html 同源 虽然在不同文件夹下
https://store.company.com/secure.html 不同源 不同的协议(https)
http://store.company.com:81/dir/index.html 不同源 不同的端口(81)
http://news.company.com/dir/other.html 不同源 不同的域名(news)
跨域的解决方法
1、 通过jsonp跨域
2、 window.name+ iframe跨域
3、 location.hash + iframe跨域
4、 document.domain + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS Cross-origin resource sharing)
7、 nginx代理跨域
8、 nodejs中间件代理跨域(node + webpack + webpack-dev-server)
9、 WebSocket协议跨域
详细介绍各个方法
(1)Jsonp 分为原生的jsonp和jQuery的jsonp
(2)CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing),主要设置在后端,只要后端实现了跨域,就实现了跨域
(3)Node中间件代理(两次跨域)
(4) Nginx反向代理
在非同源时,以下情况受到限制,无法共享
(1)Cookie、LocalStorage、IndexedDB等存储性内容
(2) DOM节点
(3) AJAX请求不能发送
在非同源时,以下情况不受同源限制(允许跨域加载)
(1) <img src=xxx> (2) <link href=xxx> (3)<script src=xxx>
jsonp的解决方式
(1) 原生的方式
如果a页面(a.com/jsonp.html) 想要得到b页面(b.com/main.js)里的数据(很明显a,b在不同的域名下,需要跨域)
在jsonp.html页面创建一个回调函数,动态的添加script元素,向服务器发送请求,请求地址为 http://b.com/main.js?callbcak=xxx,在main.js里面调用这个回调函数,并且以json的数据格式作为参数传递,完成回调。
/**
* jsonp.html
*/
let script = document.createElement('script');
script.src = 'http://b.com/main.js?callback=foo';
document.body.appendChild(script);
function foo(res) {
console.log(res);
}
/**
* main.js
*/
foo({name:'tom'})
(2) JQuery中实现跨域
JQuery会自动生成一个全局函数来代替callback=?中的问号,之后获取数据以后又会自动销毁,实际上就是起到一个临时函数的作用,$.getJSON会自动判断跨域,如果不存在跨域,会自动调用ajax,如果存在跨域,会利用异步加载的方式调用Jsonp中的回调函数
//jquery实现方式(版本一)
<script>
$.getJSON('http://example.com/data.php?callback=?', function (data) {
console.log(data);
});
</script>
//jquery实现方式(版本二)
$.ajax({
url:'http://www.xxx.com/login',
type:'GET',
dataType:'jsonp',//请求方式为jsonp
jsonpCallback:'callback',
})
function callback(res) {
console.log(res);
}
jsonp的缺点
(1) 只要用这种方式,任何页面都可以访问b页面的数据,存在安全性问题
(2)只能发送get请求,不能发送post请求
(3)可能会被注入恶意代码,篡改页面内容
使用CROS方式
浏览器将CORS跨域请求分为简单请求和非简单请求。 只要同时满足这两个条件,就属于简单请求
(1)方法: head,get,post
(2)Heder: Accept、Accept-Language、Content-Language、Content-Type(只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain )
简单请求
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求
简单请求回应
CORS请求设置的响应头字段,都以 Access-Control-开头:
Access-Control-Allow-Origin:必选
它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
Access-Control-Allow-Credentials:可选
它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers:可选
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求。
预检请求
预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。
Access-Control-Request-Method:必选
用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
Access-Control-Request-Headers:可选
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
预检请求的回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
HTTP回应中,除了关键的是Access-Control-Allow-Origin字段,其他CORS相关字段如下:
Access-Control-Allow-Methods:必选
它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Allow-Credentials:可选
该字段与简单请求时的含义相同。
Access-Control-Max-Age:可选
用来指定本次预检请求的有效期,单位为秒。
总结:
| 字段 | 简单请求 | 预检请求 |
|---|---|---|
| 请求 | Origin | Origin Access-Control-Request-Method Access-Control-Request-Headers |
| 回应 | Access-Control-Allow-Origin Access-Control-Allow-Credentials Access-Control-Expose-Headers | Access-Control-Allow-Origin Access-Control-Allow-Methods Access-Control-Allow-Headers Access-Control-Allow-Credentials Access-Control-Max-Age |
node + webpack + webpack-dev-server
(1) webpack proxy
(2) 接受客户端发送的请求以后转发给其他服务器,目的是便于开发者在开发的模式下解决跨域问题
(3) 实现代理,必须有一个中间件服务器,webpack提供的中间件服务器webpack-dev-server只适用于开发阶段
// ./webpack.config.js
const path = require('path')
devServer: {
// host: '0.0.0.0',
hot: true, /* 开启热点 */
inline: true, /* 开启页面自动刷新 */
progress: true, /* 显示打包的进度 */
disableHostCheck: true,
// port:8080,
open: true,
watchOptions: {
aggregateTimeout: 300
},
proxy: {
'/api': {
// target: 'http://100.64.34.171:8081', /* 后台服务器地址 */
target: 'http://127.0.0.1:8086',
hot: true,
ws: false,
changeOrigin: true
/* pathRewrite: {
'^/api': '' // 这里理解成用‘/api’代替target里面的地址,后面组件中我们掉接口时直接用api代替 比如我要调用'http://40.00.100.100:3002/user/add',直接写‘/api/user/add’即可
}*/
}
}
},
- target:表示代理到的目标地址
- pathWrite:默认情况下,我们在/api也会被写到url中,希望删除,需要配置
- secure默认情况下不接受转发到https服务器上,如果需要,设为false
- changeOrigin: 它是表示是否更新代理后请求的 headers 中的 host 地址
(4)实现原理
在开发阶段,webpack-dev-server会自动启动一个本地开发服务器,我们应用在开发阶段是独立运行在localhost的一个端口上的,而后端服务器又运行在另一个地址上,由于同源策略,会出现跨域。所以使用代理,【浏览器发送请求】---> 【中间服务器】---->【服务器】; 服务器和服务器之间是不会存在跨域资源问题
window.name+ iframe跨域
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
// a.html:www.domain1.com/a.html
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
alert(data);
});
// proxy.html:www.domain1.com/proxy.html
中间代理页,与a.html同域,内容为空即可
<script>
// b.html:www.domain2.com/b.html
window.name = 'This is domain2 data!';
</script>
location.hash + iframe跨域
实现原理: 相同的域的界面通过js直接访问,不同域的界面通过借助iframe的location.hash实现传值
//1.html
<iframe src="1.html" frameborder="0" id="iframe" display="none"></iframe>
<script>
var iframe = document.getElementById('iframe');
//向2.html传hash
setTimeout(function () {
iframe.src = iframe.src + "#user=aaaa"
}, 1000)
//3.html的回调
function callback(res) {
console.log('3.html' + res)
}
</script>
// 2.html
<iframe src="3.html" frameborder="0" id="iframe" display="none"></iframe>
<script>
var iframe = document.getElementById('iframe');
//监听1.html传递的hash,传给3.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash
}
</script>
//3.html
<script>
//监听2.html传递的hash
window.onhashchange = function () {
window.parent.parent.callback('didi' + location.hash.replace('#user=', ''))
}
</script>
document.domain+ iframe跨域
实现原理:此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
//from页面中
<iframe src="下一级页面地址" frameborder="0" id="iframe"></iframe>
<script>
document.domain = 'aa.com'
var user = 'admin'
</script>
//to页面中
<script>
document.domain = 'aa.com'
console.log('获取上一级页面中的变量' + window.parent.user)
</script>
webSocket
WebSocket是一种网络通信协议,它实现了浏览器与服务器全双工通信,同时允许跨域通讯。
思考💡:设置了 Access-Control-Allow-Credentials和withCredentials为什么cookie还是没能成功跨域?
withCredentials:表示XHR是否接收cookies和发送cookies
浏览器端要跨域 服务器端要检查 ,最重要的是Access-Control-Allow-Origin,标识允许哪个域的请求
(1) Access-Control-Allow-Origin:*:即使设置了withCredentials ,浏览器也不会发送cookies,因为这意味着把cookies开放给所有网站 假设当前是A网站,并且在cookie里写入了身份凭证 用户同时打开了B网站, 那么B网站给A网站的服务器发的所有请求都是以A用户的身份进行的, 这将导致CSRF问题
(2) Access-Control-Allow-Origin:xxx指定域