• 使用正确的姿势跨域


    请求跨域问题的产生原因是浏览器的同源策略(Same origin policy),这是有Netscape提出的一个著名的安全策略,对于浏览器而言,它提供了最基本也是最核心的安全功能。它所指的同源是域名、协议、端口都相同。

    从wiki百科上截了个例子说明:

    从图中可以看出,三者只要任何一个不相同,都会导致Failure.

    什么样的姿势跨域,才能成功的跨域?

    贴上一份简单的node代码,用以说明服务端的情况:

    var http = require('http');
    var fs = require('fs');
    
    var reg = /(^|?|&)callback=w*/;
    
    var MINE_TYPE = {
      'css': 'text/css',
      'html': 'text/html',
      'js': 'text/javascript',
      'txt':'text/plain'
    };
    
    http.createServer(function(req,res){
      var _url = req.url;
      var data = _url.indexOf('callback') >=0 ? req.url.match(reg)[0].substr(10)+'("XMLHttpRequest is success")':'XMLHttpRequest is success';
      res.writeHead(200,{'Content-Type':MINE_TYPE['txt'],'Access-Control-Allow-Origin':'http://192.168.1.162:9988'});
      res.end(data);
    }).listen(3003,'192.168.1.162');
    
    
    function _server(port){
      http.createServer(function(req,res){
        var pathname = req.url.substr(1);
        var _ext = pathname.split('.').pop();
        fs.readFile(pathname,'utf-8',function(err,data){
          res.writeHead(200,{'Content-Type':MINE_TYPE[_ext]});
          res.end(data)
        })
      }).listen(port,'192.168.1.162');    
    }
    
    _server(8899);
    _server(9988);

    先以普通的姿势来跨一次(本文调试使用端口条件处理同源):

    html:

    <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.js"></script>
    <div class="xmlhttprequset">发送请求</div>

    js:

    $('.xmlhttprequset').click(function(){
        $.get('http://192.168.1.162:3003',function(res){
            console.log(res);
        })
    })

    我们在浏览器url框输入192.168.1.162:8899/test.html和192.168.1.162:9988/test.html打开两个页面,分别点击页面上的"发送请求",得到以下结果:

    XMLHttpRequest cannot load http://192.168.1.162:3003/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.1.162:port' is therefore not allowed access.

    看到这个错误,心里一如既往的爽...那么爽完之后,该解决错误了,最常见的是jsonp处理跨域问题:

    js:

    $('.xmlhttprequset').click(function(){
      $.ajax({
        type:'get',
        dataType:'jsonp',
        url:'http://192.168.1.162:3003',
        success:function(res){
          console.log(res);
        }
      });
    })

    于是,我们就能够在控制台看到打印出来的东西了...

    那么我不想使用jsonp呢,能否实现跨域呢?答案是肯定的。而且还可以允许9988端口跨域请求,对8899端口进行"丑拒",就是这么任性~ 办法就是在response头配置"Access-Control-Allow-Origin"参数的值来控制给不给跨,不给跨,给跨,跨... ←_← 这真的只是回声.

    于是我们将server.js代码改了改:

    http.createServer(function(req,res){
      var _url = req.url;
      var data = _url.indexOf('callback') >=0 ? req.url.match(reg)[0].substr(10)+'("success")':'success';
      res.writeHead(200,{'Content-Type':MINE_TYPE['txt'],'Access-Control-Allow-Origin':'http://192.168.1.162:9988'});
      res.end(data);
    }).listen(3003,'192.168.1.162');

    然后将jsonp请求的代码换回get请求的代码,重启这个js,切换到浏览器,刷新2个页面,再次点击请求,效果如下:

    :9988

    success

    :8899

    XMLHttpRequest cannot load http://192.168.1.162:3003/. The 'Access-Control-Allow-Origin' header has a value 'http://192.168.1.162:9988' that is not equal to the supplied origin. Origin 'http://192.168.1.162:8899' is therefore not allowed access.
    // 强行暴击

    那么现在姿势明确了,两个姿势:正常请求+response设置允许域,jsonp。

    jsonp 实现原理

    jsonp通过添加一个<script>标签,将该标签的src指向请求资源的接口,并且需要在请求中带上一个callback参数,script的src是不受浏览器的同源策略限制的,所以只要后端将数据包装在这个callback的方法里返回即可,于是我们有了这段代码:

    server.js:

    var data = _url.indexOf('callback') >=0 ? req.url.match(reg)[0].substr(10)+'("success")':'success';
    // 如果包含callback参数,则将参数以callback的值为方法名包装成一个执行函数,否则直接返回数据

    jsonp在jQuery中的实现为:

    var oldCallbacks = [],
        rjsonp = /(=)?(?=&|$)|??/;
    
    // 默认jsonp设置
    jQuery.ajaxSetup({
        jsonp: "callback",
        jsonpCallback: function () {
            var callback = oldCallbacks.pop() || (jQuery.expando + "_" + (nonce++));
            this[callback] = true;
            return callback;
        }
    });
    
    jQuery.ajaxPrefilter("json jsonp", function (s, originalSettings, jqXHR) {
    
        var callbackName, overwritten, responseContainer,
            jsonProp = s.jsonp !== false && (rjsonp.test(s.url) ?
                "url" :
                typeof s.data === "string" &&
                (s.contentType || "")
                    .indexOf("application/x-www-form-urlencoded") === 0 &&
                rjsonp.test(s.data) && "data"
            );
    
        // jsonp 判断
        if (jsonProp || s.dataTypes[0] === "jsonp") {
    
            // 生成 callback 名称
            callbackName = s.jsonpCallback = jQuery.isFunction(s.jsonpCallback) ?
                s.jsonpCallback() :
                s.jsonpCallback;
    
            // url处理 加入 callback
            if (jsonProp) {
                s[jsonProp] = s[jsonProp].replace(rjsonp, "$1" + callbackName);
            } else if (s.jsonp !== false) {
                s.url += (rquery.test(s.url) ? "&" : "?") + s.jsonp + "=" + callbackName;
            }
    
            // 方法执行后取数据
            s.converters["script json"] = function () {
                if (!responseContainer) {
                    jQuery.error(callbackName + " was not called");
                }
                return responseContainer[0];
            };
    
            // Force json dataType
            s.dataTypes[0] = "json";
    
            // 添加callback这个方法
            overwritten = window[callbackName];
            window[callbackName] = function () {
                responseContainer = arguments;
            };
    
            // 毁尸灭迹处理
            jqXHR.always(function () {
    
                // 如果之前不存在这个方法,删除
                if (overwritten === undefined) {
                    jQuery(window).removeProp(callbackName);
    
                    // 如果之前就存在这个方法,恢复
                } else {
                    window[callbackName] = overwritten;
                }
    
                // 
                if (s[callbackName]) {
    
                    // 确保安全处理,不影响其他项
                    s.jsonpCallback = originalSettings.jsonpCallback;
    
                    // 预留着
                    oldCallbacks.push(callbackName);
                }
    
                // 如果是个函数,携带数据调用
                if (responseContainer && jQuery.isFunction(overwritten)) {
                    overwritten(responseContainer[0]);
                }
    
                responseContainer = overwritten = undefined;
            });
    
            // 归为script
            return "script";
        }
    });
    
    // jsonp实现,生成script及之后移除
    jQuery.ajaxTransport("script", function (s) {
    
        // 这个函数指明了只处理跨域请求
        if (s.crossDomain) {
            var script, callback;
            return {
                send: function (_, complete) {
                    script = jQuery("<script>").prop({
                        charset: s.scriptCharset,
                        src: s.url
                    }).on(
                        "load error",
                        callback = function (evt) {
                            script.remove();
                            callback = null;
                            if (evt) {
                                complete(evt.type === "error" ? 404 : 200, evt.type);
                            }
                        }
                        );
    
                    // 使用原生DOM操作避免一些 domManip ajax 问题
                    document.head.appendChild(script[0]);
                },
                abort: function () {
                    if (callback) {
                        callback();
                    }
                }
            };
        }
    });

    简单过一遍,就是这样了...然后扔出整理后的,不加各种判断的,简单的,只有20行代码的实现,帮助理解上面这么长长的一堆代码:

    js:

    function getInfo(url, callback, _callback){
        url += url.indexOf('?')>=0 ? '&callback=' + callback : '?callback='+callback;  // url处理
        var overWriteContent;
        var script = document.createElement('script');
        script.src = url;
        overWritten = window.dataBack;
        window[callback] = function(){
          overWriteContent = arguments[0];
        };                                                          //  生成callback方法,挂在window下
        document.head.appendChild(script);                          //  添加script标签
        script.onload = function(e){
          document.head.removeChild(script);                        //  删除script标签
          if(overWritten === undefined) delete window[callback];    //  销毁window下的callback方法
          if(e.type === 'load'){
            _callback(overWriteContent);                            //  带上数据执行回调
          } else{
            console.error('error:failed to load the resource');
          }
        }
    }

    如有不正之处,感谢指正,同时欢迎小伙伴们交流讨论~

    水平较渣,不喜勿喷,谢谢!

  • 相关阅读:
    跑步前后吃什么?
    英雄杀八人场心得
    如何判断JavaScript数据具体类型
    js实现时间日期的格式化
    各个公司前端笔试题回顾
    原型模式Prototype,constructor,__proto__详解
    二级菜单不同方法的实现
    秋招笔试碰到的疑难题目1
    php和mysql学习问题笔记
    es6学习笔记12--Class
  • 原文地址:https://www.cnblogs.com/ys-ys/p/5757905.html
Copyright © 2020-2023  润新知