我的ajax跨域方案

我的移动端web app前后端分离后,前端页面的静态资源从后端分离,交由cdn加速,而后端也不再处理页面渲染,只提供业务数据供前端通过ajax获取。
虽然这带来了喜闻乐见的跨域问题,但是现代浏览器都通过XMLHttpRequest对象实现了对CORS的原生支持,我们只需要注意有限几点就可以优雅得跨域取数据。

服务器设置允许跨域

浏览器发送的跨域请求都会有一个Origin头部,服务器需要根据这个头部信息来判断是否为合法跨域,如果接受这个跨域请求,需要在响应时在Access-Control-Allow-Origin头部回发相同的源信息。如果服务器的响应没有Access-Control-Allow-Origin头部,或信息与源信息不匹配,浏览器会驳回请求。

以Node.js的express为例:

var app = require('express')();
app.use(function(req, res, next) {
    var origin = req.header('origin');

    // 指定域名的跨域
    // if(!/baidu|qq|alibaba/.test()) return next();

    // 允许跨域
    res.header("Access-Control-Allow-Origin", origin);
    // 允许携带票据
    res.header("Access-Control-Allow-Credentials", true);

    // 允许跨域自定义的 Header
    res.header("Access-Control-Allow-Headers", "Content-Type");
    next();
});

简单请求

简单请求是指能够满足以下条件的跨域请求:

  1. 请求方法仅限于:
    GET
    HEAD
    POST

  2. 设置的请求头仅限于:
    Accept
    Accept-Language
    Content-Language
    Content-Type

  3. Content-Type的值仅限于:
    application/x-www-form-urlencoded
    multipart/form-data
    text/plain

详情参考

另外:
Webkit will force any cross-origin request to be preflighted simply if you register an onprogress event handler.

预请求(Preflighted requests)

对于不满足上面简单请求条件的跨域请求,姑且称“复杂请求”,浏览器发送前会先向服务器自动发送一个OPTIONS请求 以确保复杂请求是可以正常发出的。服务器需要作出与简单请求类似的响应。
以Node.js的express为例:

// enable pre-flight
app.options('*', function(req, res) {
    // pre-flight可被缓存的秒数
    res.header('Access-Control-Max-Age', 3);
    res.end();
});

使用cors简化服务器端配置

以上一、三中基于express的设置可用通过引入cors简化:

var app = require('express')();
// 配置跨域
//
var cors = require('cors');
app.use(cors({
    origin: /baidu|qq|alibaba/,
    credentials: true,
    maxAge: 60*60*24*100 // pre-flight时效,100天
}));
app.options('*', cors());// enable pre-flight

浏览器端发起跨域请求

传统 Ajax 指的是 XMLHttpRequest(XHR),但是XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise友好。
Fetch API 是基于 Promise 设计,可以很好的解决XHR的问题。但是Fetch自身也存在一些问题,我从使用到放弃的过程中遇到的最大问题是,不原生支持请求超时,而通过setTimeout模拟只是“自欺欺人”,很容易出现浏览器端被模拟超时中止掉之后,服务器端仍然处理了请求。

后来我就通过XHR模拟Fetch:

// fetch风格的ajax post
function _post(url, data, withCredentials = false) {
    return new Promise((resolve, reject) => {
        var req = new XMLHttpRequest();
        // 启动一个post,到指定接口,异步
        req.open('post', url, true);
        // 默认情况下,浏览器发起的跨域请求不提供票据(cookie等)
        // 当服务器设置了允许携带票据后,还要在浏览器端设置携带票据
        req.withCredentials = withCredentials;

        // 请求数据格式统一为json
        // 为了符合跨域的Simple requests要求
        // 借助Content-Language与服务器协商替代
        // 'Content-Type': 'application/json; charset=utf-8'
        req.setRequestHeader('Content-Language', 'json');
        data = JSON.stringify(data) || null;

        // 设置超时
        req.timeout = timeout;
        req.ontimeout = function() {
            reject({ message: '请求超时' });
        };

        // xhr.readystate = 4
        req.onload = function() {
            let result = req.responseText;

            // 某些情况(如服务器宕机)会导致访问req.status报错
            if(req.status < 400) {
                if(/json/.test(req.getResponseHeader('Content-Type'))) {
                    result = JSON.parse(result);
                }

                resolve(result);
            }
            else reject({ message: result, status: req.status });
        };

        // Network error
        req.onerror = function() {
            reject({ message: '网络异常' });
        }

        req.send(data);
    });
}

server端借助header的Content-Language处理json:

// config body parser
//
var bodyParser = require('body-parser');
// parse application/json
// Content-Type 为 application/json 的 cors request 不符合 simple requests,会触发 pre-flight
// 约定:Content-Type: application/json 用 content-language 含有 "json" 代替
app.use(bodyParser.json({ type: function(req) {
    return
        /json/.test(req.headers['content-type']) ||
        /json/.test(req.headers['content-language']);
} }));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容