前端跨域实践(一):CORS

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

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
前端跨域的整理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容