美文网首页
Ajax的跨域问题

Ajax的跨域问题

作者: whisper330 | 来源:发表于2019-12-26 16:52 被阅读0次

    什么是跨域及来源

    跨域问题来源于浏览器的同源策略,JavaScript只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。

    什么是同源策略

    同源策略是浏览器为安全性考虑实施的非常重要的安全策略。只有协议、端口、和域名相同,则允许相互访问。

    下面几种情况列举了不同情况下的地址,以及能否成功访问。
    • Ajax跨域代码示例
      在这个代码中,我们创建了一个oBtn按钮对象,当点击它的时候会创建名为xhr的XMLHttpRequest对象,想让它获取api.binstd.com上的天气数据。
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>ajax</title>
    </head>
    <body>
        <div id="mydiv">
            <button id="btn">点击</button>
        </div>
    </body>
    <script type="text/javascript">
        window.onload = function() {
          var oBtn = document.getElementById('btn');
          oBtn.onclick = function() {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4 && xhr.status == 200) {
                        alert( xhr.responseText );
                }
            };
            xhr.open('get', 'https://api.binstd.com/weather2/query?appkey=0baae6a71fc7209b&city=安顺&date=2018-01-01', true);
            xhr.send(); 
        };
    
    点击按钮发现浏览器提示如下,也就是说,我们的访问被拒绝了。这个就是实际的跨域问题。

    如何解决跨域问题

    1.通过JSONP解决

    JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。

    本质上是利用HTML元素的src属性都可以跨域的思路来解决的。

    imgscriptiframe等标记的src属性的值都可以赋成其它域名的合法地址。我们回想一下,在添加img标签的时候,里面有src属性,属性的链接可以是任何图片的有效链接,这时候是没有跨域的问题的。

    下图描述了用JSONP解决跨域问题的基本原理。


    该过程涉及到客户端服务器端两部分:
    • 在客户端,我们在src里面写上请求地址以及回调函数func(),并把回调函数放在全局,然后向服务器发送请求
    • 服务器在收到我们的请求以后,先准备我们请求的数据,然后返回函数执行字符串'func('+JSON.stringify(data)+')'
    • 客户端执行这条语句(回调函数)。

    现在我们用JSONP方案来改进Ajax跨域代码示例,代码如下。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>JSONP实现跨域2</title>
      </head>
      <body>
        <div id="mydiv">
            <button id="btn">点击</button>
        </div>
      </body>
      <script type="text/javascript">
        function handleResponse(response){
                console.log(response);
        }
      </script>
      <script type="text/javascript">
        window.onload = function() {
          var oBtn = document.getElementById('btn');
          oBtn.onclick = function() {     
            var script = document.createElement("script");
            script.src = "https://api.binstd.com/weather2/query?appkey=0baae6a71fc7209b&city=安顺&date=2018-01-01&callback=handleResponse";
            //在body的第一个孩子之前,插入script节点
            document.body.insertBefore(script, document.body.firstChild);   
        };
      };
      </script>
    </html>
    

    代码是在oBtn点击事件里面添加了一个script标签,它的src属性里面包含了请求地址和回调函数。
    点击按钮,请求数据成功,控制台打印如下。


    JSONP之需要注意的几点:
    1、jsonp没有使用XMLHttpRequest对象。
    2、jsonp只支持Get方式

    2.通过CORS标准解决

    细心的你可能会发现,示例代码的错误里面有这句话... origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header ...,意思是原要求被CORS政策限制了。

    什么是CORS

    CORS:全称"跨域资源共享"(Cross-origin resource sharing)。

    CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS,IE则不能低于IE10。CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送http请求并无差异。

    所以,实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。

    CORS请求类型

    CORS请求类型分为简单请求和非简单请求,后者需要预检请求。

    • 简单请求
      符合以下条件的,为简单请求
    使用下列方法之一:
    * GET
    * HEAD
    * POST
    Content-Type的值仅限于下列三者之一:
    * text/plain
    * multipart/form-data
    * application/x-www-form-urlencoded
    

    简单请求的流程如下:


    客户端:在客户端的header里面添加Origin信息,将域名写在里面,然后正常的发请求到服务器。
    服务端:服务端根据Access-Control-Allow-Origin属性来判别这个请求源是否可以请求成功。
    Access-Control-Allow-Origin:* 标识任何外域
    Access-Control-Allow-Origin: 允许访问源(多个源用,分隔)

    如果客户端的origin在上述允许源的范围内,则可以正确返回数据。这里其实相当于服务器给特定的源开一个“会员”通道,只有满足条件的才能进。

    • 非简单请求
      非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
    下图描述了该类型请求的流程:

    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

    举例来讲,现在发一个put请求,并添加header信息:

    var url = 'https://api.binstd.com/weather2';
    var xhr = new XMLHttpRequest();
    xhr.open('PUT', url, true);
    xhr.setRequestHeader('X-Custom-Header', 'value');
    xhr.send();
    

    浏览器发现这是一个非简单请求以后,就会自动发出“预检请求”,请求的一部分如下:

    OPTIONS /cors HTTP/1.1
    Origin: 原域名
    Access-Control-Request-Method: PUT //请求方式
    Access-Control-Request-Headers: X-Custom-Header //请求头
    

    服务器在收到上述的预检请求以后,检查上述字段是否在“会员通道”中,如果在的话,会确认该跨源请求,并作出回应。

    HTTP/1.1 200 OK
    ...
    Access-Control-Allow-Origin: 允许的域名 //可以为特定的域名,也可以用*代表允许任何跨源请求
    Access-Control-Allow-Methods: GET, POST, PUT //允许的方式
    Access-Control-Allow-Headers: X-Custom-Header //允许的头信息
    Content-Type: text/html; charset=utf-8
    ...
    

    一旦通过了预检请求以后,后面的就和简单请求一样了。

    3.使用代理服务器

    有时候处于安全考虑或者因为在实际开发中多个环境,在部分环境下,我们需要前端自己解决跨域问题。这个时候我们会采用一种名为代理服务器方法。

    下图为该方法的原理:



    该方法涉及到客户端,代理服务器和服务器三个部分:

    • 客户端:发送请求到代理服务器上面
    • 代理服务器:代理服务器设置CORS(代理服务器可以自己设置),并将浏览器的请求转发到服务器
    • 服务器服务器和代理服务器之间是没有同源的策略的,所以服务器可以处理代理服务器的转发请求

    代理服务器不是请求的生产者,只是请求的搬运工。

    现在我们用代理服务器方案来进行代码示例:
    需求:用代理服务器解决ajax请求天气数据的跨域问题。
    TIPS:数据接口API可以上聚合数据或者是进制数据,里面有免费数据API。

    这个方法中需要改变的是客户端和代理服务器的设置:

    • 客户端:正常发送ajax请求,将请求url写成代理服务器的地址
    ajax({
       type: 'get',
       url: 'http://localhost:7777', //代理服务器的域名
       data: {
            city: city,
            key: ************
       },
       success: resultSuccess,
       error: function (error) {
          console.log('error', error);
        }
    });
    
    function ajax (options) {
        options = options || {};   
        options.data = options.data || {};   
        var json = json(options);   
        // ajax请求   
        function json(options) {   
          // 请求方式,默认是GET
          options.type = (options.type || 'GET').toUpperCase();
          var xhr = null;    
          // 实例化XMLHttpRequest对象   
          if(window.XMLHttpRequest) {   
            xhr = new XMLHttpRequest();   
          } else {   
            // IE6及其以下版本   
            xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
          };
          // 监听事件
          xhr.onreadystatechange = function() {
            if(xhr.readyState == 4) {
              var status = xhr.status;
              if(status >= 200 && status < 300) {
                options.success(JSON.parse(xhr.responseText));
              } else {
                //options.error && options.error(status);
                options.error(status);
              }
            }
          };
          // 连接和传输数据
          if(options.type == 'GET') {
            xhr.open(options.type, options.url + '/?city='+ options.data.city + "&key=" + options.data.key, true);
            xhr.send(null);
          } else {
            xhr.open(options.type, options.url, true);
            //设置提交时的内容类型
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
            xhr.send(options.data);
          }
        }
      }   
    
    • 代理服务器:设置CORS标准,允许客户端访问,并将请求转发到真正的请求地址上。
    //设置CORS标准的两种方式
    response.writeHead(200,{"Access-Control-Allow-Origin":"http://127.0.0.1:5500/weather/index.html",
                             "Content-Type":"text/plain;charset=utf-8"});
    response.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500");
    //转发请求到聚合数据API上
    http.get('http://apis.juhe.cn/simpleWeather/query?' + parsedUrl.query, res => {
          var body = '';
    
          res.on('data', data => {
            body += data;
    });
    

    请求成功啦,header信息如下:


    代理服务器之需要注意的几点
    1、这里的CORS标准就是“会员通道”的通行证,我们还可以设置其他条件(详情见第二小节)。
    2、当使用 response.setHeader() 设置响应头时,它们将与传给 response.writeHead() 的任何响应头合并,其中 response.writeHead() 的响应头优先。详情请移步链接[9][10]

    参考链接:

    [1]轻松搞定JSONP跨域请求
    [2]珠峰培训官方
    [3]原生JS的面试题:jsonp的实现原理
    [4]HTTP访问控制(CORS)
    [5]cors实现请求跨域
    [6]跨域资源共享 CORS 详解
    [7]跨域 CORS
    [8]前端项目中nginx 本地反向代理配置
    [9]response.setHeader(name, value)
    [10]response.writeHead(statusCode[, statusMessage][, headers])

    相关文章

      网友评论

          本文标题:Ajax的跨域问题

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