可能存在跨域
- 浏览器限制。
- 跨域:不同域就会报跨域,比如端口不同,协议不同,比如前端端口是8080,ajax请求了后端8081的接口。
- XHR(XMLHttpRequest)请求,如果发出去是XHR请求,也就是说发送的不是XHR就算跨域也不会报错,因为发送xhr浏览器会去限制。
以上三个同时满足就可能会产生跨域
下面我们通过代码来模拟一下跨域,我用php和jq来写
前端代码 a.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
我是前段页面
</body>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
$.ajax({
type: 'post',
url: 'http://www.host.net/test/index.php',
data: { id:'1', name:'freddy' },
dataType: 'json',
success: function(res){
console.log(123);
console.log(res);
}
});
</script>
</html>
后端代码 index.php
<?php
echo '你能看到我的内容吗?';
配置nginx
前端nginx配置
vim /usr/local/etc/nginx/servers/a.conf
# 默认路径测试的项目
server {
listen 80;
server_name www.a.net;
access_log /usr/local/etc/nginx/logs/host_access.log main;
error_log /usr/local/etc/nginx/logs/host_error.log;
root /Users/twj/Documents/nginx_www/test/test;
index index.html index.htm index.php;
location ~ \.php$ {
root /Users/twj/Documents/nginx_www/test/test;
fastcgi_pass 127.0.0.1:9003;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME /Users/twj/Documents/nginx_www/test/test$fastcgi_script_name;
include fastcgi_params;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ /\.ht {
deny all;
}
location ~ .(JPG|jpg|gif|png|jpeg|css|js|html) {
expires 5m;
rewrite ^/abc/(.*)$ /abcd/$1 break;
}
}
后端nginx配置
vim /usr/local/etc/nginx/servers/host.conf
server {
listen 80;
server_name www.host.net;
access_log /usr/local/etc/nginx/logs/host_access.log main;
error_log /usr/local/etc/nginx/logs/host_error.log;
root /Users/twj/Documents/nginx_www/test;
index index.html index.htm index.php;
location ~ \.php$ {
root /Users/twj/Documents/nginx_www/test;
fastcgi_pass 127.0.0.1:9003;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME /Users/twj/Documents/nginx_www/test$fastcgi_script_name;
include fastcgi_params;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ /\.ht {
deny all;
}
location ~ .(JPG|jpg|gif|png|jpeg|css|js|html) {
expires 5m;
rewrite ^/abc/(.*)$ /abcd/$1 break;
}
}
这里我们就有两个域名了,
www.a.net
www.host.net
打开a.html的 f12,发现跨域了
解决跨域
xhr的请求方式是都会有跨域的,下面我们打开f12的privew
然后我再搞一个img的标签,因为img不是xhr请求方式
加入img标签的代码
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<img src="http://www.host.net/test/index.php" alt="">123
我是前端页面
</body>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
$.ajax({
type: 'post',
url: 'http://www.host.net/test/index.php',
data: {id: '1', name: 'freddy'},
dataType: 'json',
success: function (res) {
console.log(123);
console.log(res);
}
});
</script>
</html>
再按f12看看,发现img标签的没有跨域,但是ajax的却跨域了。这里就可以证明了,xhr的请求是会有跨域的风险的
跨域的请求头中多了一个这样的字段 origin,这个字段是当前的域名信息,浏览器发现是跨域请求的时候,就会产生这个参数,等请求返回来了就会检查相应头有没有支持跨域的信息,没有的话就会报错
解决方案
- 让浏览器不做限制
- xhr 的请求,让他不要发这个xhr,解决方案就是用jsonp,其实她就是和插入一个图片image一样的解决方案
- 但是跨域,往往不要用jsonp的方法
三种详细
- 被调用方让他支持跨域,让他支持基于http协议关于跨域方面的修改,比如a域名请求b域名,然后让b域名返回的信息里面加入一点信息,告诉浏览器我允许a域名访问
- 调用方隐藏跨域,通过一个代理,从浏览器发出去的都是a域名的请求,在代理里面把指定的url转到b域名里面,在浏览器看上去她就是同一个域名,就没有跨域的问题
- jsonp也可以解决跨域,前端需要表示jsonp请求类型,后端需要返回callback这个key,这就相当于是一个约定,如果后端知道是jsonp的就会返回js代码给前端
jsonp的方式
改造后的前端
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<img src="http://www.host.net/test/index.php" alt="">123
我是前端页面
</body>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
$.ajax({
type: 'post',
url: 'http://www.host.net/test/index.php',
data: {id: '1', name: 'freddy'},
dataType: 'jsonp',
jsonp: 'clalback',
success: function (res) {
console.log(res);
}
});
</script>
</html>
改造后的后端
<?php
$callback = $_GET['clalback'];
echo $callback.'('.json_encode('我是后端的东西').')';
查看f12,发现没有跨域了,也有数据返回了
jsonp的弊端
- 服务器需要改动代码支持,如果我无法碰后端代码呢?
- 只支持get
- 发送的不是xhr请求,http很多新的特性,比如异步,这些jsonp都不支持
解决跨域的两种正确思路
被调用方(基于http支持别人调用,在请求头中增加参数)可以在Nginx/apache中增加请求头参数,也可以在tomcat上增加请求头参数
支持跨域信息的配置
后端代码
header("Access-Control-Allow-Origin:http://www.baidu.com:80");// 允许所有域名访问,如果想所有就 *
header('Access-Control-Allow-Methods:POST');// 指定允许的方法 如果要所有就*
header('Access-Control-Allow-Headers:x-requested-with, content-type');// 允许前端的请求头
简单请求和非简单请求
浏览器在发起跨域请求时候会判断是否为简单请求或者是非简单请求,如果是简单请求就会先执行再判断的,意思就是会请求到后端接口,后端接口返回数据后,浏览器才会去判断是否支持跨域
何为简单请求何为非简单请求呢?
简单请求
(1)工作中比较常见 【简单请求】:
方法为:GET
HEAD
POST
(2)请求header里面:
无自定义头
Content-Type为以下几种:
text/plain
multipart/form-data
application/x-www-form-urlencoded
非简单请求
put,delete方法的ajax请求
发送json格式的ajax请求
带自定义头的ajax请求
option预检命令
非简单请求的跨域请求中,如果出现这个值就说明他说先判断后执行的,会发起一个option(预检命令)请求。这里面他会询问服务器是否允许这个请求头content-type
这时候下面这段代码就起作用了
header('Access-Control-Allow-Headers:x-requested-with, content-type');// 允许前端的请求头
然后option(预检命令)检查通过的时候,才会真正的把请求发出去,也就是post
option预检命令缓存
很多语言中都会支持下面的这些配置的。
php、py、go、java 都是会有这个header的设置函数的。
header("Access-Control-Allow-Origin:http://www.baidu.com:80");// 允许所有域名访问,如果想所有就 *
header('Access-Control-Allow-Methods:POST');// 指定允许的方法 如果要所有就*
header('Access-Control-Allow-Headers:x-requested-with, content-type');// 允许前端的请求头
header('Access-Control-Max-Age:3600');// 在一定时间内,对预检命令进行缓存,这时候就不用发送option请求了
header("Access-Control-Allow-Credentials:true");///支持cookie额跨域,允许后端跨域设置Cookie。
// 注意!!!如果带cookie的话,header("Access-Control-Allow-Origin:http://www.baidu.com:80"); 就不能用 *
// 如果是这样设置了,那岂不是只能允许指定域了?那我其他地方来的请求岂不是没办法了?
// 这时候可以利用浏览器头部origin参数
$headers = getallheaders()// 获取请求头信息,然后再去获取Origin
if ($headers) {
header("Access-Control-Allow-Origin:$headers");
}
调用方通过nginx解决跨域,有时候我们无法操作调用方的服务器,所以只能在调用方中通过反向代理来解决跨域
server {
listen 80;
server_name a.com
# 反向代理解决跨域
location /ajaxserver {
proxy_pass http://localhost8080/test/
}
}