美文网首页
前端跨域实践(一):CORS

前端跨域实践(一):CORS

作者: 一个笑点低的妹纸 | 来源:发表于2017-07-29 14:17 被阅读882次

    上周做了一个移动端表单提交的页面,其中涉及到了跨域问题,想来也是惭愧,因为之前一直都没有遇到过这个问题,因此都没有深入探索过,只是知道有哪几种方式,这次终于借这个机会可以把遗留的知识点补一补了。

    1. CORS(Cross-Origin Resource Sharing,跨源资源共享)

    【基本思想】:使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

    【实现方式】:
    浏览器在发送请求时,检测到跨域时,会自动加上一个额外的 Origin 头部,其中包含请求页面的原信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。

    Origin: http://www.nczonline.net
    

    如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的原信息(如果是公共资源,可以回发“*”)。

    Access-Control-Allow-Origin: http://www.nczonline.net
    

    如果没有这个头部,或者有着头部但原信息不匹配,浏览器就会驳回请求。

    注意:默认情况下,请求和响应都不包含 cookie 信息。

    2. 跨域场景复原

    为了模拟跨域,在自己的本地起了2个服务,一个采用 webpack 充当静态资源服务器(webpack 脚手架可参考:scaffoldsForFE),另一个用 Node 搭建,充当接受请求的服务器,分别给两个服务器分配了不同的端口号。

    Client:http://localhost:8000

    var oDiv = document.getElementById('content');
    
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {   // 响应接收完毕后将触发 onload 事件
        if (xhr.readyState == 4) {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                oDiv.innerHTML = 'Request was success:' + xhr.responseText;
                console.log('Request was success:', xhr.responseText);
            } else {
                oDiv.innerHTML = "Request was unsuccessful: " + xhr.status;
                console.log("Request was unsuccessful: ", xhr.status);
            }
        }
    }
    
    xhr.open('get', 'http://localhost:8000', true);  // 不跨域
    // xhr.open('get', 'http://localhost:8888', true);  // 跨域
    xhr.send();
    

    Server:http://localhost:8888

    var http = require('http');
    
    http.createServer(function(req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.write('This is a server test page.');
        res.end();
    }).listen(8888);
    

    当 Client 端向 http://localhost:8000 发请求时,不存在跨域,能够成功返回页面信息,此时的页面及其发请求的 Request Headers 如下:

    不存在跨域

    当 Client 端向 http://localhost:8888 请求服务的时候,由于存在跨域问题,无法获得响应:

    Not Allowed Access

    但是其请求的头部自动带上了 Origin 字段,而且由于是默认情况,没有带上 Cookie:

    Request Headers

    解决的方式是,在 Server 端响应的时候,在 Access-Control-Allow-Origin 头部中回发相应的原信息:

    var http = require('http');
    
    http.createServer(function(req, res) {
        // 设置响应头部
        res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000');
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.write('This is a server test page.');
        res.end();
    }).listen(8888);
    

    再重新请求,成功获得响应信息,且请求没有带上 Cookie:

    Allowed Access

    由于在跨域时,默认情况下是不允许客户端向服务器发送请求时带上 Cookie 的,那怎样才能带上 Cookie 呢?需要同时在客户端和服务端同时设置相应字段:

    (1)客户端在请求中打开 withCredentials 属性,指定某个请求应该发送凭据:

    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    

    (2)服务端在响应头部中添加 Access-Control-Allow-Credentials 字段,且设为 true,表明服务器接受带凭据的请求:

    res.setHeader('Access-Control-Allow-Credentials', true);
    
    请求带上 Cookie

    如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给 Javascript,则 responseText 是空字符串,status 的值为0,而且会调用 onerror() 事件处理程序。

    cookie 是可以设置访问域的,在设置 cookie 的时候,设定了 cookie 的访问域名为一个顶级域名,则可以达到几个子域名共享 cookie 的效果,如腾讯网 www.qq.com 与微信网页版 wx.qq.com 共享了 pac_uid,关于前端存储的相关内容,可参考我之前在博客园写的博文:前端存储调研总结

    3. 模拟 GET 带参数 及 POST 跨域请求

    i. GET 带参数跨域请求

    Client 端:通过在URL后面加上查询参数

    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    
    xhr.onload = function() {   // 响应接收完毕后将触发 onload 事件
        // 处理 xhr.responseText
    }
    
    xhr.open('get', 'http://localhost:8888?method=GET&name=Ruth', true);
    xhr.send();
    

    Server 端:处理 GET 请求

    var http = require('http');
    var url = require('url');
    
    http.createServer(function(req, res) {
        res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000');
        res.setHeader('Access-Control-Allow-Credentials', true);
        res.writeHead(200, {'Content-Type': 'text/plain'});
    
        // 解析 url 参数
        var params = url.parse(req.url, true).query;
        res.write('请求类型:' + params.method);
        res.write('<br />');
        res.write('姓名:' + params.name);
        res.end();
    }).listen(8888);
    
    GET with Query
    ii. POST 跨域请求

    不同于 JSONP,CORS 的好处就是可以让我们实现 POST 请求。

    Client 端发送信息:

    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    
    xhr.onload = function() {   // 响应接收完毕后将触发 onload 事件
        // 处理 xhr.responseText
    }
    
    xhr.open('post', 'http://localhost:8888', true);
    xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  // 设置请求头
    xhr.send('method=POST&name=Ruth'); 
    

    Server 端处理请求:

    var http = require('http');
    var querystring = require('querystring');
    
    http.createServer(function(req, res) {
        res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000');
        res.setHeader('Access-Control-Allow-Credentials', true);
        res.writeHead(200, {'Content-Type': 'text/plain'});
    
        var post = '';
        // 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
        req.on('data', function(chunk) {
            post += chunk;
        });
    
        // 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回
        req.on('end', function() {
            post = querystring.parse(post);
            res.write('请求类型:' + post.method);
            res.write('<br/>')
            res.write('姓名:' + post.name);
            res.end();
        });
    }).listent(8888);
    
    POST request

    4. 参考资料

    阮一峰-跨域资源共享 CORS 详解
    AJAX POST&跨域 解决方案 - CORS
    前端跨域的整理

    相关文章

      网友评论

          本文标题:前端跨域实践(一):CORS

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