Http浅析【2】——ajax跨域问题

视频参考:ajax跨域完全讲解

本文精华版:【综合】ajax跨域问题

什么是跨域问题

简单来讲,当前台调用后台,如果接口不是一个域时,就会产生跨域问题。

由于目前前后端分离的技术架构,前台项目和后台项目都是独立开发进行的。当联调测试时,前台页面必然会大量调用后台的接口,此时如果接口不是同一个域,就会发生跨域问题。

测试环境搭建

【后台环境搭建】

使用node+express搭建一个后台系统
(1)新建一个文件夹NodeBack,输入 npm init 初始化项目,创建index.js作为整个后台的入口文件

(2)安装express:npm install express –save

(3)安装body-parse:npm install body-parse–save,用于解析post请求的参数

(4)编写入口文件 index.js

var express = require('express');

/**
 * 解析post来的参数
 app.use(bodyParser.json()); // for parsing application/json
 app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
 * */

var bodyParser = require('body-parser');

var app = express();

app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded

app.get('/test', function (req, res) {
  var name = req.query.name
  var age = req.query.age
  res.jsonp({
    data:'name is ' + name + "| age is " + age
  });
});

app.post('/login', function (req, res) {
  var name = req.body.name
  var password = req.body.password
  if (password === "123") {
    res.jsonp({
      "name": name,
      "password": password
    });
  } else {
    res.send("error!");
  }
});

var server = app.listen(3005, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});

GET请求

POST 请求

【前台请求页面】


简单html版

简单起见,就使用Jquery来发送前台请求

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

  <!--导入jquery-->
  <script src="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<a href="#" onclick="getReq()">发送get请求</a>

<script>
  function getReq(){
    $.getJSON("http://localhost:3005/test?name=hugo&&age=18")
      .then((result)=>{
        console.log(result)
    })
  }
</script>
</body>
</html>

使用firefox,打开此网址,按F12,调出控制台,点击发出get请求

但是在控制台发现报错提示,这就是跨域问题

ajax跨域问题分析

发生跨域问题有三个原因:
【1】浏览器限制:很多同学认为跨域问题的是服务器后台不允许前台跨域访问,但一些情况下并不是这样,还有可能是浏览器多管闲事,处于安全考虑限制了跨域访问。

如何验证这种想法呢,我们可以在get请求中增加一句log

app.get('/test', function (req, res) {
  var name = req.query.name
  var age = req.query.age
  res.jsonp({
    data:'name is ' + name + "| age is " + age
  });
  console.log("receive get request")
});

再次发送请求:


发现响应都是没有任何问题的

【2】跨域,地址中,协议、域名、端口任意一个不一样,浏览器就认为是跨域

【3】XHR(XMLHttpRequest)请求。请求的type为XHR,非json、img等其他类型。
当上述三种情况同时满足时,跨域问题就产生了,

解决思路

【1】解决浏览器限制:通过指定浏览器的启动参数,解除限制,但是这种情况需要用户手动操作,所以交互不友好,所以不推荐使用

【2】转变请求的类型。上面说到,只有请求类型是xhr时,将xhr的类型转化为jsonp的格式

【3】跨域。
被调用(服务器)方支持跨域;
调用方做跨域支持(隐藏跨域):通过代理,从浏览器发送出的请求都是同源请求

全面解决跨域问题

【禁止浏览器检查跨域】

不做介绍,因为实用价值不大。

【jsonp解决跨域】

json for pending :利用js请求资源标签时,请求可以跨域,来解决跨域请求,它是一种变通的解决方案。

function jsonpReq(){
    var result
    $.ajax({
      url:"http://localhost:3005/test?name=hugo&&age=18",
      dataType:'jsonp',
      success:function(json){
        result=json
        console.log(result)
      }
    })
  }

在使用jsonp的时候,后台需要做改动(java后台),不然服务器返回的内容,浏览器会当做js代码来解析,会报语法错误。经测试,node编写的后台无需添加额外的内容

我们对jsonp的原理进行分析,解释为什么java后台需要进行额外设置:


首先,看一下jsonp发送的报文类型是script,在之前分析跨域问题产生的条件之一就是请求报文的类型是xhr,那么jsonp的思路就是改变报文的请求类型,变为script。

第二,普通的ajax请求返回的json对象,而jsonp返回的是js脚本

第三,jsonp发送的url后面接了一串额外的callback字符
i
那么从这里我们就能简单分析jsonp的原理是:jsonp发送的url请求自动添加了一个callback参数,当后台发现callback参数时,就认为是一个jsonp请求,响应头在返回数据时,数据格式就由json变成script,而script的内容就是一个函数调用。

那么java后台的改动就能分析得出如下:

所以,如果我们把url请求中的callback改成callback2,那么后台就不会认为前台请求是一个jsonp请求。即:前台调用的“callback”和后台的“callback”实际上是一个接口上的协议,需要保持一致

很明显,Jsonp是基于Jquery的一种解决方案,但是在react、vue这类本身的是要替代Jquery操作dom结构的框架,效果可想而知。

接下来,再来谈谈jsonp的利弊:

1、服务器端需要改动。当服务器是第三方提供时,这种做法就会出现局限性。
2、只支持GET请求,通过分析原理,我们知道jsonp是通过Jquery动态创建一个script来创建一个请求的,所以请求类型就被局限在了GET。
3、 发送的不是XHR请求,即XHR的诸多特性都无法使用,例如异步、事件机制都无法使用。

综上可见,我们在解决跨域问题时,最好方法还是解决请求是跨域的,而不是改变请求是XHR还是JSONP

下面就继续介绍本文的重点:【让服务器支持跨域】、【前台请求隐藏跨域】

【从系统架构分析跨域】

最常见的javaee 架构

请求流程:
请求从浏览器发出,首先到apache或nginx(静态服务器) ,然后判断客户端请求是静态请求还是动态请求。

简单讲,跟客户端数据有关系的就是动态数据(用户信息,消息列表等),而图片、js、css、html等就是静态请求。

如果是静态请求,静态服务器直接处理,然后把数据返回给客户端。如果是动态请求,则转发给后台的应用>服务器,处理完毕后,应用服务器把数据返回给静态服务,随后静态服务器发送给客户端。

中间的http服务器,一般有两个作用:
(1)处理静态请求
(2)转发动态请求和负载均衡。

那么在看跨域请求时的两种思路:

第一:直接从浏览器里发出请求到被调用方的http服务器。 这时需要在http服务器上做响应的修改,这些修改都是基于http请求关于跨域部分的协议。就是在返回头里增加一些字段,告诉浏览器被调用方允许客户端跨域访问。这样浏览器就不会报跨域的问题。

第二:隐藏跨域。请求不直接从浏览器发出,而是从中间http服务器转出。通过服务器的转发,浏览器不会发现所有的请求都是同一个域。即调用方会发现所有的请求都是从本地的http服务器发出(其实并不是,请求都是经过客户端http服务器的转发)

跨域解决方向-被调用方解决-Filter及spring解决方案

这是基于支持跨域的解决思路,是基于http协议关于跨域方面的一些规定,在响应头里增加指定的字段,告诉浏览器服务端支持跨域调用。 此种情况下,浏览器直接发送请求到服务方。

在据图解释此方案前,我们理清几个问题:

浏览器如何判断是否跨域?

答:通过对比普通请求和跨域请求浏览器里的请求头里的内容,可以发现跨域请求中增加了一个origin字段,字段值为当前域名的信息。也就是说,当浏览器发现请求是跨域时,就会在请求头添加origin的字段。当浏览器接收到响应头后,就会检查里面是否有允许跨域的字段,如果没有,就会报错。

浏览器是先执行?还是先判断?

答:浏览器发送跨域请求给后台,后台日志打印正常,浏览器响应状态码为200,但却报跨域问题,说明浏览器是先执行请求操作,然后在判断是否跨域。那么,是不是所有的请求都是先执行后判断呢?

并不是的,这就引出了简单请求和非简单请求的概念。

简单请求和非简单请求

每次浏览器再发送请求时,会做一次判断,如果是简单请求,那么就是先执行再判断。如果是非简单请求,那么浏览器会先发送一个预检头,检查通过后,浏览器才会发送真正的请求

跨域解决——服务器端实现【Spring 编写的后台】

基于注解,可以使用

@CrossOrigin

那么被注解类下的所有请求方法都是可以支持跨域的

跨域解决——服务器端实现【Spring boot 编写的后台】

这里我们需要了解http协议关于跨域方面所有的要求,针对不同的场景返回不同的头。只有知道了所有的响应头,我们才能在后面的http服务器上配置响应头。

首先需要注册一个filterRegistrationBean ,然后让所有的请求都经过这filter

接下来我们在CrosFilter里的doFilter方法中添加服务器响应头

对于非简单请求,我们在按照上述方法添加响应头后,可能会出现如下的错误:


意思是我们的返回头缺少“access-control-allow-headers”是缺少的。

所以我们需要在后台服务器中再增加一行代码:

res.addHeader("Access-Control-Allow-Headers","Content-type");

在上面我们分析了什么是简单请求和非简单请求,那么在非简单请求的情况下,浏览器会发送两次请求:

这样会影响请求的效率。http协议中增添了一个请求头,对预检头缓存。

res.addHeader("Access-Control-Max-Age","3600");

告诉浏览器缓存预检头,时间为3600s。那么当再次发送跨域请求时,就只会请求一次。

问题:*代表所有的Url请求,那么是不是在任何情况下都没有问题呢?

带cookie的跨域

随着项目模块越来越多,很多模块现在都是独立部署。模块之间的交流有时可能会通过cookie来完成。比如说门户和应用,分别部署在不同的机器或者web容器中,假如用户登陆之后会在浏览器客户端写入cookie(记录着用户上下文信息),应用想要获取门户下的cookie,这就产生了cookie跨域的问题。

当出现带cookie的跨域问题时,浏览器会报如下错误:

即*无法满足带cookie的情景,如果把header改为特定的地址

res.addHeader("Access-Control-Allow-origin","http://localhost:8081");

则可能会出现如下错误:

接下来我们继续设置

//enable cookie
res.addHeader("Access-Control-Allow-Credentials","true");

但随之而来的问题也比较明显,那就是我们显然不能把origin里的地址写死,所以我们需要在http请求中取出orign字段,然后设置在响应头当中。

这样就完成了带cookie的跨域调用。这里再总结一下:

【1】带cookie的跨域时,Access-Control-Allow-Origin 不能设置为*
【2】发送的cookie的被调用发(服务器)的cookie,而不是调用方(浏览器)的cookie

带自定义头的跨域

当我们直接在跨域请求中添加自定义请求头时(假设自定义请求头为x-header1和x-header2),可能会报如下错误:

这里的解决办法是将自定义的请求头添加到Access-Control_Allow-Headers:

res.addHeader("Access-Control_Allow-Headers","Content-type,x-header1,x-header2");

当然我们也可以动态地设置headers

跨域解决——服务器端实现【Node.js 编写的后台】

跨域解决方向-被调用方解决-Http服务器解决方案

【Nginx配置】

Nginx配置文件:

【apache配置】

相当于把之前在nginx上的配置,再在apache上配置一下,思路也是和nginx一样的。由于apache配置比较复杂,详细配置请参考其他文章。

跨域解决方向-调用方解决

这是基于隐藏跨域的解决思路。此种情况下,请求不会直接从浏览器发送到被调用方,而是从调用方的http服务器转发过去的(被调用方http服务器或者应用服务器接收)。使用了反向代理技术,在浏览器上是看不到任何跨域请求。

这里就和上述apache和nginx操作类型,需要修改他们的配置文件(反向代理的配置)。本文只做简单的配置解释,详细内容请小伙伴们自行百度。

Nginx反向代理

首先我们修改一下host,其效果是当你在浏览器访问 www.a.com和www.b.com时,url地址会映射到127.0.0.1上

接下来配置Nginx配置

然后客户端在进行网络请求时,需要变成nginx代理之后的地址,也就是

/ajaxserver

可以发现,此时调用的url就是本域的地址(浏览器看到的是相对地址),自然就不存在了跨域问题。

apache反向代理配置

思路:增加一个虚拟主机,然后让此虚拟主机,把我们的跨域请求作反向代理

总结

至此,本文简单分析了一下跨域问题产生的原因和常见的解决方法,如果有什么问题欢迎留言交流讨论~

笔者个人订阅号~欢迎小伙伴们关注
微信公众号-感谢关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值