温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

WEB跨域请求

发布时间:2020-06-29 11:25:01 来源:网络 阅读:719 作者:ttlxihuan 栏目:web开发

web开发随着ajax的出来带来了革命性的变化,它改变了web的数据加载方式让交互更友好,网络资源更节省。但最初ajax考虑安全性并没有开放跨域请求,随着H5的到来ajax开放了跨域请求,所以ajax跨域请求存在兼容性,不过现在的浏览器大部分都已经支持了。


常用跨域请求手段有:

  1. jsonp

  2. FORM到页面框架

  3. HTTP服务器代理

  4. 服务器脚本转发

  5. ajax

  6. WebSocket

  7. 插件



jsonp

这种方式是早期在ajax不支持跨域请求时的一种替代方案应用非常多,在JQuery类的早期框架都集成了此功能。期原理就是通过HTML的<script>标签加载一个跨域的请求地址并指定一个随机回调函数,所连接的服务器返回指定的回调函数并增加参数,标签加载完后会自动执行代码来完成请求回调,因此jsonp只支持GET请求方式,并且需要服务器作专用处理,典型的示例如:

前端代码:(域名www.a.cn请求域名www.b.cn)

(function (global) {
    //发送请求
    function request(url, data, callback) {
        function jsonpcall(_data, error) {
            callback(_data, error);
            //清理
            delete global[data['jsonpcallback']];
            document.body.removeChild(script);
        }
        data = data || {};
        //生成全局回调函数
        data['jsonpcallback'] = 'jsonp_' + String(Math.random()).substr(2, 10);
        global[data['jsonpcallback']] = jsonpcall;
        var params = getParamsString(data);
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + params;
        script.onerror = function (event) {
            jsonpcall(null, event);
        };
        document.body.appendChild(script);
    }
    //转换请求参数
    function getParamsString(data, prefix) {
        var arr = [];
        prefix = prefix || '';
        for (var key in data) {
            var name = '';
            if (!prefix) {
                name = key;
            } else {
                name = prefix + '[' + key + ']';
            }
            if (typeof data[key] === 'object') {
                arr.push(getParamsString(data[key], name));
            } else {
                arr.push(name + '=' + data[key]);
            }
        }
        return arr.join('&');
    }
    global.jsonp = request;
})(window);
//发起请求
jsonp('http://www.b.cn', {id: 12}, function () {
    console.info(arguments);
});


服务端代码:(域名www.b.cn,以PHP为例)


<?php
if (empty($_GET['jsonpcallback'])) {
    header('HTTP/1.1 404 Not Found');
    die();
}
//查数据,返回结果
$data = [
    'status' => 'ok',
    'msg' => '操作成功'
];
//输出结果
echo $_GET['jsonpcallback'] . '(' . json_encode($data, JSON_UNESCAPED_UNICODE) . ');';


这种方式请求不受跨域限制使用方便,兼容性好,非常适用小数据量跨域请求。缺点是只支持GET请求无法完成像上传文件或其它请求方式的操作,服务器响应结果专用性强。




FORM到页面框架

这种方式在异步上传文件使用非常多,通常是iframe+form结合通过form的target属性指定到iframe的name完成异步请求,但iframe有部分移动端存浏览器不支持,另外还有一个frameset标签在H5中已经不支持。

使用这种方式跨域请求兼容性仅仅在PC端较好而且服务端不需要作额外处理,但移动端上使用需要留意用户平台是否都支持。典型的示例如:

// 请求处理
function request(form, callback) {
    var iframe = document.createElement('iframe'), name = 'IFRAME-' + String(Math.random()).replace('.', ''), submit = 1;
    iframe.name = name, form.target = name;
    iframe.onload = function () {
        //初次加载
        if (submit === 1) {
            form.submit();
        } else if (submit === 2) {
            try {
                var json = JSON.parse((iframe.contentDocument || iframe.contentWindow.document).body.innerHTML);
                callback(json);
            } catch (err) {
                callback(null, err);
            }
            document.body.removeChild(iframe);
        }
        submit++;
    };
    iframe.onerror = function (event) {
        callback(null, event);
        document.body.removeChild(iframe);
    };
    //不能显示标签
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
}

//发送请求
request(document.forms[0], function(){
    console.info(arguments);
});


这种方式间接完成跨域操作,同时对服务端的代码没有额外要求,只需要返回一个通用的json串即可,通过这个方式可以完成GET与POST请求并且还可以上传文件,最大的缺点是有少部分浏览器不支持。



HTTP服务器代理

这种方式从理论上说不需要开发额外的代码只需要在HTTP服务器上配置代理转发即可以满足所有请求的跨域请求,但会额外增加服务器性能开销,不适合于跨域请求过多的场景,毕竟服务器的资源是有限的。一般使用这种方式的基本上是小项目,大项目这么玩在硬件上开销不容忽视。以nginx为例,典型的配置代码有:(仅以server块为例)

    location /proxy {
          # resolver 114.114.114.114 223.5.5.5 valid=3600s;
          proxy_pass http://www.b.com:8099/;
    }

nginx的proxy_pass做代理配置很容易,配置灵活。

  • 如果在proxy_pass中指定了详细地址则只转发到对应的地址

  • 如果没有指定地址则把当前的地址追加到域名下

  • 如果需要指定DNS解析地址则可以增加resolver命令,但一定要保证DNS服务器正常,否则请求会卡住

  • 转发可以使用IP地址和域名,如果有特定端口一定要追加上

  • 域名可以使用变量来传入,例如:set $proxy_host "http://www.b.com:8099/";  proxy_pass $proxy_host;

  • 可以指定路径,也可以通过if来判断

nginx配置代理调试比较麻烦需要多注意两个日志文件。对于配置要考虑:

  • 是否被截取即没有进入代理块

  • 代理域名端口协议是否正确

  • 域名或地址是否能正常访问



服务器脚本转发

这种方式是通过服务器的脚本来转发,避开前端跨域请求,开发方便,对于配置HTTP服务器有限制或问题时是一个比较简单的替代方案。本质与HTTP服务器代理类似,都是通过服务器来转来,不可避免增加服务器的开销。这种处理方式唯一的好处是开发调试容易,兼容性好。以PHP为例,典型的示例代码如:

$url = $_GET['_url_'];
$input = file_get_contents('php://input');
$curl = curl_init();
curl_setopt_array($curl, [
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36',
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => FALSE, // 不验证证书
    CURLOPT_SSL_VERIFYHOST => FALSE, // 不验证域名
]);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $input);
    curl_setopt($curl, CURLOPT_URL, $url);
} else {
    unset($_GET['_url_']);
    $params = http_build_query($_GET);
    curl_setopt($curl, CURLOPT_URL, $url . (strpos($url, '?') ? '&' : '?') . $params);
}
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
header('HTTP/1.1 ' . $httpCode);
echo $response;

代码中简单的示例通过curl发起跨域请求,前端只需要指定需要请求的跨域地址和参数即可完成请求操作,由于请求类型多样化,所以使用代码转发要做到兼容所有请求形式则需要做较多的处理,一般用不上。




ajax

ajax支持跨域可以说可以非常理想,遗憾的时推出的晚,不过现在随着浏览器全面支持H5使得ajax跨域请求变得普及起来,如果应用平台涉及比较老的浏览器则需要留意了。官方文档:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 在官方文档中有比较详细的说明。

其实从本质上说跨域限制只是浏览器出于安全考虑的一个限制,而开放这个限制也需要合理安全,否则会造成比较多的跨域请求漏洞,因此浏览器对ajax跨域请求作了一些限制,它必需要服务器返回允许跨域响应头信息,才正常提供响应结果,所以ajax跨域请求需要服务端额外增加响应头信息。

ajax跨域请求分为两种场景:基本请求,预检请求;并且XMLHttpRequest内部通过指定条件强制判断识别并作出相应的操作,其中预检请求会触发CORS预检,CORS预检会把请求类型强制修改为OPTIONS类型向服务器发起跨域请求检查服务器是否响应允许跨域请求头信息如果允许则再次发起指定的请求类型跨域请求并把响应内容注入到ajax响应内容中,而基本请求是直接发起指定请求类型的跨域请求。两种请求场景均需要跨域服务器返回允许跨域响应头信息,响应状态等头信息不会受影响。

常规请求

这种场景浏览器会直接发起请求,当响应头信息内容中不包含Access-Control-Allow-Origin允许头信息时会丢弃响应内容并向控制台发出Failed to load警告信息,提示服务器没有Access-Control-Allow-Origin响应头信息请求不被允许。

条件:(需全部满足)

  • 请求类型为 GET、HEAD、POST

  • 仅设置过Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width头信息,或使用默认的不设置头信息

  • Content-Type只能设置为application/x-www-form-urlencoded、multipart/form-data、text/plain,或者不设置

  • 在请求中没有使用过XMLHttpRequestUpload事件监听。XMLHttpRequestUpload是通过XMLHttpRequest对象的upload属性获取的一个上传进度对象,可以获取上传进度数据。

  • 请求中没有使用ReadableStream对象。ReadableStream大部分浏览器还不支持,是一个获取响应二进度流的对象处理器。

示例代码:(只在不触犯以上任何一个条件即可)

(function (global) {
    function request(url, callback, data, method) {
        var xmlHttp = new XMLHttpRequest();
        var params = getParamsString(data || {});
        method = method || 'GET';
        if (method.toUpperCase() === 'POST') {
            xmlHttp.open(method, url, true);
            xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;');
            xmlHttp.send(params);
        } else {
            xmlHttp.open(method, url + (url.indexOf('?') >= 0 ? '&' : '?') + params, true);
            xmlHttp.send(null);
        }
        xmlHttp.onreadystatechange = function () {
            if (xmlHttp.readyState == 4) {
                try {
                    if (xmlHttp.status == 200) {
                        var json = JSON.parse(xmlHttp.responseText);
                        callback(json);
                    } else {
                        callback(null, xmlHttp.responseText);
                    }
                } catch (err) {
                    callback(null, err);
                }
            }
        };
    }
    //转换请求参数
    function getParamsString(data, prefix) {
        var arr = [];
        prefix = prefix || '';
        for (var key in data) {
            var name = '';
            if (!prefix) {
                name = key;
            } else {
                name = prefix + '[' + key + ']';
            }
            if (typeof data[key] === 'object') {
                arr.push(getParamsString(data[key], name));
            } else {
                arr.push(name + '=' + data[key]);
            }
        }
        return arr.join('&');
    }
    global.ajax = request;
})(window);
//发起请求
ajax('https://www.b.cn/', function () {
    console.info(arguments);
});


预检请求

这种场景请求浏览器会强制把请求类型改为OPTIONS类型发起预检跨域请求,当响应头信息内容中不包含Access-Control-Allow-Origin允许头信息时会丢弃响应内容并向控制台发出Failed to load警告信息,提示服务器没有Access-Control-Allow-Origin响应头信息请求不被允许;如果响应头信息全部允许跨域请求则浏览器会再次发送指定请求类型的跨域请求到服务器并获取本次响应内容注入到ajax的响应内容中。

条件:(任意条满足)

  • 请求类型指定为:PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH

  • 设置了Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width之外的头信息

  • 设置Content-Type是为application/x-www-form-urlencoded、multipart/form-data、text/plain之外的类型

  • 在请求中使用过XMLHttpRequestUpload事件监听

  • 请求中使用ReadableStream对象

示例代码:(强制增加一个特殊头信息即可)

(function (global) {
    function request(url, callback, data, method) {
        var xmlHttp = new XMLHttpRequest();
        var params = getParamsString(data || {});
        method = method || 'GET';
        if (method.toUpperCase() === 'POST') {
            xmlHttp.open(method, url, true);
            xmlHttp.setRequestHeader('Ajax-Request', '1');
            xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;');
            xmlHttp.send(params);
        } else {
            xmlHttp.open(method, url + (url.indexOf('?') >= 0 ? '&' : '?') + params, true);
            xmlHttp.setRequestHeader('Ajax-Request', '1');
            xmlHttp.setRequestHeader('Content-Type', 'text/html');
            xmlHttp.send(null);
        }
        xmlHttp.onreadystatechange = function () {
            if (xmlHttp.readyState == 4) {
                try {
                    if (xmlHttp.status == 200) {
                        var json = JSON.parse(xmlHttp.responseText);
                        callback(json);
                    } else {
                        callback(null, xmlHttp.responseText);
                    }
                } catch (err) {
                    callback(null, err);
                }
            }
        };
    }
    //转换请求参数
    function getParamsString(data, prefix) {
        var arr = [];
        prefix = prefix || '';
        for (var key in data) {
            var name = '';
            if (!prefix) {
                name = key;
            } else {
                name = prefix + '[' + key + ']';
            }
            if (typeof data[key] === 'object') {
                arr.push(getParamsString(data[key], name));
            } else {
                arr.push(name + '=' + data[key]);
            }
        }
        return arr.join('&');
    }
    global.ajax = request;
})(window);
//发起请求
ajax('https://www.b.cn/', function () {
    console.info(arguments);
});


携带请求资源

在跨域请求时还允许携带请求资源如cookie,但必需设置XMLHttpRequest对象的withCredentials属性为true,如:

xmlHttp.withCredentials = true;

携带的请求资源受,响应头信息Access-Control-Allow-Origin、Access-Control-Allow-Credentials两个影响,Access-Control-Allow-Origin必需指定为当前请求的域名,Access-Control-Allow-Credentials必需设置为true否则跨域请求携带资源失败即服务器返回的cookie不会被记录。跨域请求只受Access-Control-Allow-Origin影响,只要Access-Control-Allow-Origin允许当前域名则请求可以正常获取响应结果。

注意:如果设置这个值,Access-Control-Allow-Origin指定为通配符有兼容问题,需要设置为对应的域名集。


跨域请求响应头信息

这些头信息由服务器响应,浏览器内核判断并作相应处理。这些头信息是预定好的,只需要按规则响应返回给浏览即可完成跨域请求。浏览器对这些头信息处理略有差异,不建议设计的太随意毕竟安全还是比较重要的。

Access-Control-Allow-Origin

该头信息是跨域请求中必需响应的,它用来标识服务器允许哪些请求来源域名共享数据,一般如果没有特别要求最好设置为指定的开放域名。如果允许任意域名使用*通配符。如果是指定一个域名并且每次请求该响应头均会变化则需要增加Vary: Origin头信息来禁止浏览器的缓存。


Access-Control-Expose-Headers

该头信息允许暴露给外部的头信息,默认只可以暴露服务器响应Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma头信息。如果想让脚本通过XMLHttpRequest对象的getAllResponseHeaders()函数访问到其它头信息则可以在这里设置,多个以逗号分开也可以使用通配符*。注意预检请求中询问成功后才有效。


Access-Control-Max-Age

该状信息用于预检请求中Access-Control-Allow-Methods、Access-Control-Allow-Headers信息可以缓存多久,单位为秒,每个浏览器中最大秒数有限制,火狐中最大86400秒,谷歌中600秒,如果需要禁用缓存响应返回-1即可。


Access-Control-Allow-Credentials

该头信息是允许跨域请求中携带请求资源主要是cookie。只要在XMLHttpRequest对象中设置withCredentials=true同时Access-Control-Allow-Origin为当前请求的域名,Access-Control-Allow-Credentials为true(字符串)即可携带资源请求。


Access-Control-Allow-Methods

该头信息是允许跨域请求类型集,可以设置为指定的请求类型如:GET、POST、PUT、DELETE等,也可以设置为*通配符允许所有请求类型,默认不设置即允许所有请求。该头信息仅在预检请求中有效,标准要求是当发送OPTIONS请求时如果响应中允许当前指定请求类型时会自动再发起一次指定的请求类型请求并对XMLHttpRequest对象作出回调,目前各浏览器实现并不统一,有的浏览器此参数无效。


Access-Control-Allow-Headers

该头信息用于开放额外发送给服务器的头信息,默认必定允许Accept、Accept-Language、Content-Language、Content-Type等常规允许头信息设置,当指定其它头信息时浏览器启动预检请求询问服务器是否允许额外的这些头信息设置,如果允许则再发送常规请求。开放的头信息多个以逗号分开也可以使用通配符*(通配符有兼容问题)。如果头信息不能匹配允许则请求终止不再发起常规请求并且XMLHttpRequest获取响应失败代码无法获取服务器的响应内容(有兼容性问题部分浏览器还是可以获取服务器响应内容)。


跨域禁止修改请求头信息

这些请求头信息在跨域请求时浏览器禁止代码修改。在非跨域请求则允许修改。

Origin

标记请求来源站点。


Access-Control-Request-Method

用于通知服务器真实的请求类型。


Access-Control-Request-Headers

用于通知服务器请求头字段集,多个以逗号分开或使用通配符*


兼容性

跨域请求是新开放的特性在兼容性方面差异比较多,一般推荐使用常规请求会更理想,使用预检模式下很多代码或服务器均未对OPTIONS请求类型提供支持导致请求失败。对于浏览器内核兼容性可查看:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Browser_compatibility



WebSocket

WebSocket是web引入的全新技术,让web端也能实现长连接,目前兼容性 https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7 。websocket允许跨域连接并且不需要额外处理,因为是长连接,所以web服务器需要额外调整为长连接才能与终端通信。websocket连接操作简单,但服务器端需要转换处理方式,一般应用与游戏、直播类项目较多。


兼容性

WebSocket请求是专门为H5设计的,目前支付H5的浏览器均支持。在部分浏览器中https与http页面中使用ws与wss有安全限制,要求https只能连接wss,而http的可以使用ws和wss。

一般在https页面中最好使用wss协议连接,如果服务端不支持wss可以通过nginx进行转发把ws改为wss。



插件

增加插件可以扩展web功能。插件不受浏览器过多的限制,在跨域请求中可以很好的发辉。缺点是插件的开发形式不统一,兼容性也不一样,目前比较多的是flash,但flash自身的问题已经将慢慢进入历史舞台了。


向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI