背景
- 这个写的人太多了,我觉得很多人少说了很多东西,导致一大部分人配置跨域都不明白或者直接失败,所以总结下。
简单请求与复杂请求
- 学习跨域前,需要先了解如何会产生跨域问题,以及简单请求和复杂请求。
- 跨域是浏览器限制,浏览器发起请求才会有的,服务端对服务端之类是没有跨域限制的。
- 跨域就是只要域名不同或者域名相同端口号不同就会有跨域。
- 简单请求就是正常的get或者post请求,不设置额外请求头之类的东西。
- 而复杂请求就是除了get的所有请求方式或者get和post加了自定义的请求头。
- 为什么要说简单请求和复杂请求呢?因为复杂请求在跨域的时候会发一个预检请求options。有很多人看了博客说跨域怎么怎么设置,结果服务端对预检请求没有响应或者没设置允许预检请求导致无法跨域。而简单请求就不会发预检请求。但也是需要跨域的。
- 另外说一下cookie,在没有跨域的情况下,是可以携带cookie访问的,但是如果你携带cookie跨域就必须进行设置,而且服务端和客户端必须设置,否则报错。
解决跨域的几种方式
一、Jsonp
- jsonp是各种地方有吹到,实际这个方法可以理解成拼url。
- 原理就是利用script的src可以跨域,这个src的url可以拼参数,于是就通过参数传递给服务端,服务端拿了参数经过自己逻辑返回js代码。
- 这个js代码基本上没啥限制,改dom什么的都可以,但是最常用的是触发页面上一个固定的方法:
let btn =document.querySelector('#ccc')
btn.addEventListener('click',function(){
let script = document.createElement('script')
script.src = 'http://127.0.0.1:3000/jsonp?callback=xxx'//xxx和下面函数对应,callback和服务端对应
document.body.appendChild(script)
})
function xxx(data){
console.log(data);
}
- 比如这个例子,url拼的是callback=xxx,xxx就和页面上函数相对应,服务端代码:
if(pathname==='/jsonp'){//jsonp不会发跨域请求头
res.end(`${query.callback}('a=1')`)//相当于xxx('a=1')
- 这就相当于
xxx('a=1')
这样页面上的xxx函数即可拿到data这个参数进行回调。 - 由于这个模式用的比较多,jquery做了层包装,不需要你写函数名,反正都是对应页面上同名函数的,于是jquery的jsonp就会变成下面格式:
$.ajax({
url: "http://localhost:3000/jsonp",
type: "GET",
dataType: "jsonp",
success: function (data) {
var result = JSON.stringify(data);
$("#text").val(result);
}
});
- 这种形式就让不懂得里面道理的人感觉jsonp好牛b,可以回调,能传参数什么的,实际上就是script拼个url而已。等同于上面那个写函数名的例子。
二、CORS
- 这种方式是比较通用的解决方法,由于ajax发送跨域请求,会自动带上
origin
,如果服务端没配置允许跨域请求头,则默认不允许,浏览器直接报错。所以像防盗链什么的都是看请求头(referer)返回服务端指定的资源。 - 先看一下服务端设置:
if(req.headers.origin){
res.setHeader('Access-Control-Allow-Origin',req.headers.origin)
res.setHeader('Access-Control-Allow-Credentials',true)//携带凭证
res.setHeader('Access-Control-Allow-Headers','token')
res.setHeader('Access-Control-Allow-Methods','PUT,DELETE,OPTIONS')
//预检请求时间 单位 秒
res.setHeader('Access-Control-Max-Age','10')
if(req.method==='OPTIONS'){
return res.end()//preflight
}
}
- 前面几个响应头看名字就知道什么意思,有需要就进行配置,主要说一下预检请求。
- 前面说了复杂请求会发预检请求,而预检请求它的请求方式是
OPTIONS
,而不是get post之类,很特别吧?所以在允许跨域方式里必须把OPTIONS给加上,不然预检请求都过不来。另外服务端还必须配置对预检请求的响应,如果预检请求收不到回复,那么它就觉得链接有问题,后面的请求就不会发送。所以对于复杂请求,这2个设置很重要。 - 再说一下携带资源凭证,就是跨域携带cookies。服务端配置携带凭证就可以,请求方还需要配置携带凭证:
let request = new XMLHttpRequest
request.open('post','http://127.0.0.1:3000/user',true)
request.onreadystatechange=function(){
if(request.readyState===4){;
if(request.status>=200&&request.status<=400){
console.log(request.response)
}
}
}
request.withCredentials=true//跨域携带凭证
request.send('a=1&b=2')
- 请求方设置个true即可。
三、对web服务器做代理
- 一般前端做的网页是起在web服务器上,这个web服务器有很多,比如nginx,vueserver,webpackserver,liveserver这些乱七八糟的都是起了个服务。后端一般也是起了个服务,因为服务端口号不一样,这就导致跨域问题,而代理是需要这个web服务器做的,当然也有那种服务端模板渲染模式,就是服务端和页面在同一服务下,那就没跨域问题。
- 原理简单说是这样:页面去请求一个地址,这个地址是没有跨域的比如
http://127.0.0.1:3000/api
,但实际web服务器会把这个地址包装一下,比如做成http://127.0.0.1:5000/api
发出去,于是浏览器认为自己没有发跨域请求,但web服务器通过代理给你做成了跨域从而请求到结果。 - 由于web服务器众多,配置也不一样,所以有需要自行百度。
代码
- 最后放一下服务端和页面代码,方便各位复制粘贴。
- 服务端:
const http = require('http')
const url = require('url')
const path = require('path')
http.createServer((req,res)=>{
let {pathname , query}=url.parse(req.url,true)//pathname是/下的路径,query是?后
req.on('data',()=>{
//不监听data会导致卡住
})
req.on('end',()=>{
if(req.headers.origin){
res.setHeader('Access-Control-Allow-Origin',req.headers.origin)
res.setHeader('Access-Control-Allow-Credentials',true)//携带凭证
res.setHeader('Access-Control-Allow-Headers','token')
res.setHeader('Access-Control-Allow-Methods','PUT,DELETE,OPTIONS')
//预检请求时间 单位 秒
res.setHeader('Access-Control-Max-Age','10')
if(req.method==='OPTIONS'){
return res.end()//preflight
}
if(pathname==='/user'){
if(req.method==='GET'){
return res.end('123')
}else{
return res.end('sad')
}
}
}
if(pathname==='/jsonp'){//jsonp不会发跨域请求头
res.end(`${query.callback}('a=1')`)//相当于xxx('a=1')
//res.end('let p=document.createTextNode("213");document.body.appendChild(p)')
}
})
}).listen(3000)
- 页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>123123</div>
<button id='ccc'>12313</button>
<script>
let request = new XMLHttpRequest
request.open('post','http://127.0.0.1:3000/user',true)
request.onreadystatechange=function(){
if(request.readyState===4){;
if(request.status>=200&&request.status<=400){
console.log(request.response)
}
}
}
request.withCredentials=true//跨域携带凭证
request.send('a=1&b=2')
let btn =document.querySelector('#ccc')
btn.addEventListener('click',function(){
let script = document.createElement('script')
script.src = 'http://127.0.0.1:3000/jsonp?callback=xxx'//xxx和下面函数对应,callback和服务端对应
document.body.appendChild(script)
})
function xxx(data){
console.log(data);
}
</script>
</body>
</html>