Blame view

doraemon/middleware/htaccess/index.js 5.58 KB
htoooth authored
1
/**
htoooth authored
2
 * 后端:改写和跳转:中间件。
htoooth authored
3 4
 *
 * 说明:
htoooth authored
5 6
 * 该中间件使用:使用文件夹下 rule 的所有的文件,以文件名的形式,载入该模块
 * 文件的名字建议以网站子域名的形式存在,如:guang, item
htoooth authored
7 8 9
 * 每个文件夹是一个模块。
 *
 * 模块的导出形式:见 guang 模块的使用。
htoooth authored
10 11 12 13 14 15 16 17
 * Created by TaoHuang on 2017/2/21.
 */

'use strict';

const fs = require('fs');
const _ = require('lodash');
const path = require('path');
htoooth authored
18
const helpers = global.yoho.helpers;
htoooth authored
19
const logger = global.yoho.logger;
htoooth authored
20
htoooth authored
21
const TYPE = require('./type');
htoooth authored
22
const DIR = './rules/';
htoooth authored
23
const curDir = path.resolve(__dirname, DIR);
htoooth authored
24 25 26 27
const files = fs.readdirSync(curDir);

let domainRules = {};
htoooth authored
28
// 启动时就载入模块
htoooth authored
29 30 31
files.forEach((file) => {
    let info = fs.statSync(path.resolve(curDir, file));
htoooth authored
32 33 34 35 36 37 38
    if (info.isFile()) {
        let module = path.basename(file, '.js');
        let loadPath = DIR + module;

        try {
            domainRules[module] = require(loadPath);
        } catch (e) {
htoooth authored
39
            logger.error('load rules wrong', e);
htoooth authored
40
        }
htoooth authored
41 42 43
    }
});
htoooth authored
44
// 选择模块
htoooth authored
45
const loadRule = (domain) => {
yyq authored
46
    return domainRules[domain] || domainRules.default;
htoooth authored
47 48
};
htoooth authored
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
// 已处理完
class Done {
    constructor(val) {
        this.__val = val;
    }

    map(fn) {
        return new Done(fn(this.__val));
    }

    val() {
        return this.__val;
    }

    static of(val) {
        return new Done(val);
    }
}

const stepX = (fn) => {
    return (maybe) => {
        if (maybe instanceof Done) {
            return maybe;
        }
        return fn(maybe);
    };
};

// 判断是否需要处理
const isNeedHandle = (req, rule) => {
    return (_.isRegExp(rule.origin) && rule.origin.test(req.url)) ||
        (_.isFunction(rule.origin) && rule.origin(req) ||
        (_.isString(rule.origin) && _.isEqual(req.url, rule.origin)));
};

// 正则
const step1 = (req, rule, url) => {
    if (_.isRegExp(rule.origin)) {
        if (_.isFunction(rule.target)) {
            return Done.of(url.replace(rule.origin, _.partial(rule.target, req)));
        } else if (_.isString(rule.target)) {
            return Done.of(url.replace(rule.origin, rule.target));
        }
    }

    return url;
};

// 函数
const step2 = (req, rule, url) => {
    if (_.isFunction(rule.origin)) {
        if (_.isFunction(rule.target)) {
            return Done.of(rule.target(req));
        } else if (_.isString(rule.target)) {
            return Done.of(rule.target);
        }
    }

    return url;
};

// 字符
const step3 = (req, rule, url) => {
    if (_.isString(rule.origin)) {
        if (_.isFunction(rule.target)) {
            return Done.of(rule.target(req));
        } else if (_.isString(rule.target)) {
            return Done.of(rule.target);
        }
    }

    return url;
};

const getResultStatus = (req, rule, newUrl) => {
    if (newUrl instanceof Done) {
        if (newUrl.val() === req.url) {
            return {
                needNext: true,
                needRedirect: false,
                process: false
            };
        } else if (rule.type === TYPE.redirect) {
            return {
                needNext: false,
                needRedirect: true,
                process: true,
                url: newUrl.val()
            };
        } else if (rule.type === TYPE.rewrite) {
            return {
                needNext: true,
                needRedirect: false,
                process: true,
                url: newUrl.val()
            };
        }
    }

    return {
        needNext: true,
        needRedirect: false,
        process: false
    };
};

const isJsonp = (req) => {
    return _.includes(req.url, 'callback');
};
htoooth authored
159
/**
htoooth authored
160 161 162 163
 * 1. origin 可接受是 正则 , 函数, 纯字符串
 * 2. target 可接受是 匹配字符串 , 函数, 纯字符串
 * 3. 301: 跳转
 *    rewrite: 改写 url
htoooth authored
164 165
 * @returns {Function}
 */
htoooth authored
166 167
module.exports = () => {
    return (req, res, next) => {
htoooth authored
168 169 170 171
        if (req.subdomains.length > 1 && req.subdomains[1] === 'www') {
            return res.redirect(301, helpers.urlFormat(req.path, req.query || '', req.subdomains[0]));
        }
htoooth authored
172
        req.isMobile = /(nokia|iphone|android|ipad|motorola|^mot\-|softbank|foma|docomo|kddi|up\.browser|up\.link|htc|dopod|blazer|netfront|helio|hosin|huawei|novarra|CoolPad|webos|techfaith|palmsource|blackberry|alcatel|amoi|ktouch|nexian|samsung|^sam\-|s[cg]h|^lge|ericsson|philips|sagem|wellcom|bunjalloo|maui|symbian|smartphone|midp|wap|phone|windows ce|iemobile|^spice|^bird|^zte\-|longcos|pantech|gionee|^sie\-|portalmmm|jig\s browser|hiptop|^ucweb|^benq|haier|^lct|opera\s*mobi|opera\*mini|320x320|240x320|176x220)/i.test(req.get('user-agent')); // eslint-disable-line
htoooth authored
173
htoooth authored
174
        if (req.xhr || isJsonp(req)) {
htoooth authored
175 176 177
            return next();
        }
htoooth authored
178
        let rules = loadRule(req.subdomains[0]);
htoooth authored
179
        let useRule = _.find(rules, rule => isNeedHandle(req, rule));
htoooth authored
180
htoooth authored
181
        if (!useRule) {
htoooth authored
182 183 184
            return next();
        }
htoooth authored
185 186 187 188 189 190 191 192 193 194 195
        let step1x = stepX(_.partial(step1, req, useRule, _));
        let step2x = stepX(_.partial(step2, req, useRule, _));
        let step3x = stepX(_.partial(step3, req, useRule, _));
        let processAfter = _.partial(getResultStatus, req, useRule, _);
        let processing = _.flow(step1x, step2x, step3x);

        let process = _.flow(processing, processAfter);
        let result = process(req.url);

        if (result.process) {
            if (result.needRedirect) {
htoooth authored
196
                return res.redirect(301, result.url);
htoooth authored
197 198 199 200 201
            }

            if (result.needNext) {
                req.url = result.url;
                return next();
htoooth authored
202 203 204 205 206 207
            }
        }

        return next();
    };
};