同源:
同源:ajax请求的url和网页所在的url具有相同的协议,域名,ip和端口
同源策略:为了数据安全,浏览器禁止通过ajax请求读取非同源的数据(注意:同源策略的两个关键点:1,浏览器 2,ajax请求)
同源策略下,浏览器会拦截跨域数据并报错,拦截的响应数据
跨域/跨源:ajax请求的url和网页所在的url具有不同的协议,域名,ip和端口
1,解决跨域请求报错的三种解决办法
1.1 COR头跨域
原理:在ajax请求的响应头中,添加cors字段,相当于令牌/通行证,可以避免浏览器拦截这个跨域数据(浏览器在接受ajax跨域数据时,会优先判断响应头中是否存在cors字段,如果有,则不拦截数据,如果没有,则报错)
步骤:在后端服务器接口响应之前,添加响应头
// 在响应头中添加cors字段, 允许这个接口跨域访问
res.setHeader("Access-Control-Allow-Origin", "*")
//或
// 使用第三方cors模块允许跨域 (可一次设置所有接口跨域)
var cors = require("cors")
app.use(cors())
前端代码:
<h1 id="h1">这是主页</h1>
<script>
// 访问目标服务器的登录接口
$.get("http://localhost:5000/login?name=张三", function(data){
h1.innerText = data
})
</script>
- 优点:简单方便,容易操作,前端请求无需修改ajax代码
- 缺点:数据安全性降低,完全受后端限制,前端无法控制
1.2 jsonp跨域
原理:同源策略只对ajax请求有限制,非ajax请求无限制,非ajax请求(如:form表单请求、标签属性请求,a标签的href,img标签的src,script标签的src)不会被同源策略拦截,所以使用script标签的src属性发起的跨域请求不会被同源策略拦截。
步骤:前端(3种):
1,原生的jsonp请求:
前端:
<script>
function myMethod(res){
console.log(res)
alert(res.name)
}
</script>
<script src="http://192.168.124.43:3000/userInfo?callback=myMethod"></script>
- callback这个字段不能自定义,是jsonp内部规定好的字段,不用callback调用不了
后端:
app.get('/userInfo', function(req, res) {
console.log(req.query)
req.query.callback({ name: "李四" })
})
2,常规的开发习惯写法
前端:
<script>
function myMethod2(res){
alert(res.name)
}
$("<script>").attr("src", "http://192.168.124.43:3000/userInfo?callback=myMethod2").appendTo("body")
</script>
后端同1一样
3,jquery封装jsonp请求,使用$.getJSON()实现
前端:
<script>
// 由于jquery封装后,回调函数是匿名函数, 所以参数写成 callback=? ?代表后边匿名函数
$.getJSON("http://192.168.124.43:3000/userInfo?callback=?", function(res) {
alert(res.name)
})
</script>
后端:
app.get('/userInfo', function(req, res) {
console.log(req.query)
res.jsonp({ name: '李四' })
})
- jsonp()函数内部会自动调用get请求参数种的callback字段传入的回调函数,jsonp()参数作为callback回调函数的参数返回响应
- jsonp函数的作用有两个:1,调用参数种的callback回调函数 2,返回响应数据
优点:不降低数据安全性,不完全受后端控制,相比于代理服务器跨域更简单
缺点:比cors头跨域复杂,受客户端和服务器共同控制,原理较复杂
1.3 代理服务器 跨域
原理:同源策略只对浏览器发起的ajax请求有限制,非浏览器请求无限制,所以可以使用网页所在的同源服务器发起ajax跨域请求,获取跨域数据后返回给同源客户端
步骤:创建同源服务器,在服务器种添加代理
前端:
<h1 id="h1">这是主页</h1>
<script>
// 访问目标服务器的登录接口
$.get("/myApi/login?name=张三", function(data) {
h1.innerText += data
})
// 访问目标服务器的注册接口
$.post("/myApi/register?name=张三", function(data) {
h1.innerText += data
})
</script>
后端:
var proxy = require("http-proxy-middleware").createProxyMiddleware
app.use("/myApi", proxy({
// 这个对象是代理的配置对象
target: "http://localhost:5000", //目标服务器地址
changeOrigin: true, // 修改请求路径
pathRewrite: { // 路径修改方案
"^/myApi": ""
}
}))
// http://localhost:3000/myApi/login?name=张三
// 以上请求url代理后的url如下
// http://localhost:5000/login?name=张三
// http://localhost:3000/myApi/register?name=张三
// 以上请求url代理后的url如下
// http://localhost:5000/register?name=张三
- 使用代理服务器跨域必须在前端服务器接口种下载并导入代理模块,并直接创建代理函数
- 使用代理模块,调用代理函数,实现请求代理,这相当于创建了一个请求接口,同同源网页index.html访问,而参数"/myApi"就是同源请求的接口地址
2,四种ajax请求方式
2.1 原生的ajax请求方式
get请求方式
var xhr = new XMLHttpRequest();
xhr.open("get", "http://open.douyucdn.cn/api/RoomApi/live?page=1")
xhr.send()
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
console.log(1, JSON.parse(xhr.responseText))
}
}
post请求方式:
var xhr = new XMLHttpRequest()
xhr.open("post", "http://open.douyucdn.cn/api/RoomApi/live")
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
xhr.send("page=1")
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
console.log(2, xhr.responseText);
}
}
- 第一步:创建一个xhr对象 var xhr = new XMLHttpRequest();
- 第二步:设置请求方式和请求URL xhr.open( ) 参数1为请求方式get/post,如果请求方式是get,则参数2的请求URL带传递参数;如果为post,则参数2的请求URL不带传递参数(即不带URL中的 ?后边的一系列参数)
- 第三步:发送请求 xhr.send(); get请求,send不带参数;post请求,send参数是post请求的请求体, 也是键值对结构
- 第四步:监听readystatechange ,判断readyState的请求准备状态是不是4;请求准备状态有1-4,1表示xhr创建;2表示数据处理完成;3表示请求已发送;4表示服务器返回结果,请求完成 responseText: "用户名已存在,注册失败"
2.2 jquery封装的ajax请求方式
$.get("http://open.douyucdn.cn/api/RoomApi/live", {
page: 1
}, function(data) {
console.log(3.1, data);
})
$.post("http://open.douyucdn.cn/api/RoomApi/live", {
page: 1
}, function(data) {
console.log(3.2, data);
})
$.ajax({
type: "get",
url: "http://open.douyucdn.cn/api/RoomApi/live",
data: {
page: 1
},
success: function(data) {
console.log(3.3, data);
}
})
2.3 axios封装的ajax请求方式
使用axios前必须引入插件
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
get请求方式:
axios.get("http://open.douyucdn.cn/api/RoomApi/live", {
params: {
page: 1
}
}).then(function(res) {
console.log(4.1, res.data);
})
post请求方式:
axios.post("http://open.douyucdn.cn/api/RoomApi/live", "page=1", {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).then(function(res) {
console.log(4.2, res.data);
})
- post请求数据没有在params字段中,get请求数据在params字段中
- axios请求的回调函数在then函数中,而不是第三个参数
2.4 fetch函数请求
ES6中提供了一个函数fetch,是对原生ajax的封装,不需要插件,直接调用
fetch("http://open.douyucdn.cn/api/RoomApi/live?page=1").then(function(res) {
// res是响应信息对象,里边没有响应体数据,需要使用json函数解析后拿到响应体
console.log(4, res);
var obj = res.json(); //调用json函数解析数据,返回的是promise对象
console.log(5, obj);
obj.then(function(data) { //使用promise对象调用then函数拿到数据
console.log(data);
})
//实际开发直接写
res.json().then(function(data) {
console.log(6, data);
})
})
- 响应数据也是通过then函数拿到,而回调函数的res是响应对象,没有响应体数据,需要解析之后才能拿到响应体数据,只能解析一次。解析之后是个promise对象直接调用then拿到data数据
报错:TypeError: Response.json: Body has already been consumed.(响应体已经被解析过了)
原因:res调用json()只能调用一次,也就是只能解析一次