美文网首页vue传值我爱编程
Http浅析【2】——ajax跨域问题

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

作者: 一颗语法糖 | 来源:发表于2018-04-10 17:22 被阅读70次

    视频参考: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.comwww.b.com时,url地址会映射到127.0.0.1上

    接下来配置Nginx配置


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

    /ajaxserver

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

    apache反向代理配置

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

    总结

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

    笔者个人订阅号~欢迎小伙伴们关注


    微信公众号-感谢关注

    相关文章

      网友评论

      • IT人故事会:老铁下次注意格式啊,不太清晰,给点建议对于新手需要的是注释啊
        一颗语法糖:我自己也发现了_(:з」∠)_,因为跟着视频走的缘故,线理得不够清楚,下次注意~

      本文标题:Http浅析【2】——ajax跨域问题

      本文链接:https://www.haomeiwen.com/subject/wkslqftx.html