Skip to content

Instantly share code, notes, and snippets.

@michael-ji0406
Created April 14, 2016 10:32
Show Gist options
  • Select an option

  • Save michael-ji0406/1def1da4ef5e90ed8195c805435092a8 to your computer and use it in GitHub Desktop.

Select an option

Save michael-ji0406/1def1da4ef5e90ed8195c805435092a8 to your computer and use it in GitHub Desktop.
/**
* HTTP Server Settings
* (sails.config.http)
*
* Configuration for the underlying HTTP server in Sails.
* Only applies to HTTP requests (not WebSockets)
*
* For more information on configuration, check out:
* http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.http.html
*/
var jade=require('jade');
var merge=require('merge');
var MobileDetect=require('mobile-detect');
var httpProxy = require('http-proxy');
var geoip = require('geoip-lite');
var fs = require('fs');
var _ = require('lodash');
var url = require('url');
//======对于 post 请求,每分钟仅允许3个post请求
var Ddos = require('ddos-express')({
rules:[{regexp: ".*",maxWeight: 3}],
checkInterval:1000*60,
logFunction:function(ip,path) {
Monitor.error('ip request many time------->'+ip);
console.log(path);
}
}
);
var proxy = httpProxy.createProxyServer({});
// var _ = require('lodash');
var config=require('./i18n'),
helper=require('../helper/global'),
util=helper.helper,
api=require('../services/API');
module.exports.http = {
/****************************************************************************
* *
* Express middleware to use for every Sails request. To add custom *
* middleware to the mix, add a function to the middleware config object and *
* add its key to the "order" array. The $custom key is reserved for *
* backwards-compatibility with Sails v0.9.x apps that use the *
* `customMiddleware` config option. *
* *
****************************************************************************/
middleware: {
// if requested paths contains uppercase letter,
// then transform it to lowercase and redirect it
process_url: function(req, res, next) {
//api don't need to be transformed
if(/^\/api\/.*/.test(req.path) || util.is_assets(req.url)) return next();
if (/[A-Z]/.test(req.path)) {
// Assemble new url
var newUrl = url.format({
pathname: req.path.toLowerCase(),
query: req.query
});
return res.redirect(301, newUrl);
}
next();
},
//========处理多语言
i18n_locale: function (req,res,next) {
if(util.is_assets(req.url)){
return next(); // 资源不需要经过此中间件
}
//======如果路径中包含 .字符,则忽略后面的字符串, 并在结尾加/,以跳转
var pathname = (req["_parsedUrl"]||{}).pathname;
if(pathname.has('.')){ req.url = req.url.split('.')[0] + "/"; }
var locales = config.i18n.locales,
def = config.i18n.defaultLocale,
url = req.url+"/",
reg = new RegExp("^\/("+locales.join('|')+")(\/|\\?|#)",'g'),
match = url.match(reg),
match_lang = match && match[0].replace(/\/|\?|#/g,''),
lang = match_lang||def;
var map = { cn:'zh-CN' , kr:'ko' ,'zh-tw':'zh-TW' ,'jp':'ja'}, // locale map
lang_map = { jp:'ja' }; //lang map
req.locale_key = req.locale = map[lang]?map[lang]:lang;
req.lang = lang_map[lang] || lang;
req.url = req.url.replace(/(\?|&)?cache=(no|update)$/,''); //replace update cache flag : cache=update
req.origin_url = req.url;
req.support_lang = ['en','cn','ja','kr','de','fr','zh-tw','es'];
if(lang){//路由重定向
req.url = (req.url+"/").replace("/"+lang+"/","/");
if(!req.url) req.url = '/';
//===== 去除路径末尾添加的反斜杠
if(req.url!='/') req.url = req.url.replace(/\/$/g,'');
}
//======如果url末尾包含 / 则跳转到不包含反 / 的url
if(req.origin_url!='/' && /.*\/$/g.test(req.origin_url)){
Monitor.green(req.origin_url);
return res.redirect(301,req.origin_url.replace(/\/$/g,''));
}
//=====根据匹配出的语言,跳转到指定路径
var redirect_map = {en:'',ja:'jp',ko:'kr'};
var match_to = redirect_map[match_lang];
if(typeof match_to !== 'undefined'){
return res.redirect(301,req.originalUrl.replace('/'+match_lang,match_to));
}
next();//触发下一个任务
},
//==========根据ip选择语言
ip_locale: function(req,res,next) {
if(util.is_assets(req.url)){
return next(); // 资源不需要经过此中间件
}
//======= set req cookie domain
var origin_url = settings.origin,
origin = origin_url.replace('://','').has(':') ? origin_url : (origin_url+":") ;
req.cookie_domain = ".dji.com";
if(origin.match(/(:\/\/)?www(.*):/g)){
req.cookie_domain = RegExp.$2; //重正则匹配中获取domain
}
req.geo_country = req.cookies['www_country']||"unknown";//默认country,从cookie里取出
req.ip_from = (req.headers['x-forwarded-for'] || '').split(',')[0] || req.connection.remoteAddress || req.ip;
req.local_ip = settings.local_ip; //本机IP地址,需在前端 console 出来
res.setHeader('local_ip', req.local_ip);
if(!!req.query["clear_lang_cookie"] || !!req.query['cache']){ //当带参数lang_cookie时,不做语言跳转
next(); //触发下一个任务
}else if(!util.is_assets(req.url)){
var cookie_lang = req.cookies['www_lang'];
var domain = req.cookie_domain;
var lang_options = { maxAge: '2592000000' , path: '/' , domain: domain};
if (_.isEmpty(cookie_lang) ){
//1、没有www_lang 则判断ip
var ip = req.ip_from;
var geo = geoip.lookup(ip)||{};
req.geo_country = geo.country;
res.cookie('www_country', geo.country||'unknown', lang_options );
if(req.lang == 'en'){
//不带语言请求,则根据ip判断国家
var map = {
'CN' : 'cn',
'TW' : 'zh-tw',
'HK' : 'zh-tw',
'MO' : 'zh-tw',
'JP' : 'jp',
'KR' : 'kr',
'DE' : 'de',
'US' : 'en',
'FR' : 'fr',
'ES' : 'es'
},
lang = map[geo.country];
if( !_.isEmpty(geo) && lang && lang != 'en' ){
//ip判断的国家不为英文
res.cookie('www_lang', lang, lang_options );
var redirect_url = ('/'+ lang + (req.url=='/' ?'': req.url));
return res.redirect(redirect_url);
}else{
//无法识别国家或ip判断的国家为英文,不跳转
res.cookie('www_lang', 'en', lang_options);
next();
}
}else{
//带语言请求,不做跳转
var www_lang = (req.lang == 'ja' ? 'jp' : req.lang);
res.cookie('www_lang', www_lang, lang_options);
next();
}
}else{
next(); //触发下一个任务
}
}else{
next(); //触发下一个任务
}
},
is_mobile:function(req,res,next){
if(util.is_assets(req.url)){
return next(); // 资源不需要经过此中间件
}
var userAgent = req.headers['user-agent']||'';
var media = new MobileDetect(userAgent);
req.is_mobile = media.mobile()&&!media.tablet();
req.is_weixin = media.match('MicroMessenger');
req.media = media;
req.x_protocol = req.headers['x-forwarded-proto'] || req.protocol;
//如果cookie或者查询参数中包含设定字段,强制使用 mobile 视图
if(req.cookies['from']==='mobile'||req.query['from']==='mobile'){
req.is_mobile = true;
}
//如果是移动设备,且环境允许
req.use_mobile = req.is_mobile;
//如果存在,此cookie,则认为是由手机版,切换到pc版,不做跳转处理
if(req.cookies['www_visit_pc']){
req.use_mobile = false;
req.visit_pc = true;
}
req.device = req.use_mobile ? 'mobile' : 'pc';
if(userAgent.has('##DDS@DJI##')) req.device = 'dds';
if(!util.is_assets(req.url)){
Monitor.green('request time--------->' + new Date());
Monitor.green('-----------request agent--------------');
Monitor.green(userAgent);
Monitor.green('---->ip-from:'+(req.ip_from||req.ip)+"; country:"+(req.geo_country||"unknown") +"; protocol:" + req.x_protocol);
Monitor.green('---->mobile:'+media.mobile()+'; os:'+media.os()+"; agent:"+media.userAgent()+"; weixin:"+req.is_weixin);
Monitor.green('-------------------------------------');
}
var www_from = req.cookies['www_from'],
cookie_opt = { maxAge: '2592000000' , path: '/' , domain: req.cookie_domain};
//如果是移动设备,则跳转到移动设备的链接上
if(req.use_mobile && !/^\/mobile/g.test(req.url) && !util.is_assets(req.url) && req.method.toUpperCase() != 'POST'){
var lang = (req.lang=='en' ? '' : '/'+req.lang);
if(www_from!=='mobile'){
res.cookie('www_from', 'mobile', cookie_opt);
}
var redirect_m_url = (lang + '/mobile'+req.url).replace(/\/$/, '');
return res.redirect(redirect_m_url);
}else{
if(!util.is_assets(req.url)){
//======= 如果不是mobile, 但路径中带了 mobile, 就跳转 ( cache=update 操作除外 )
var is_redirect = !req.use_mobile && /^\/mobile/g.test(req.url) && !req.query['cache'];
req.url = req.url.replace(/^(\/mobile)|(\/dds)/g,'');
if(!req.use_mobile && www_from!=='pc'){
res.cookie('www_from', 'pc', cookie_opt);
}
if(is_redirect){
var redirect_url = req.originalUrl.replace(/(\/mobile)|(\/dds)/g,'');
return res.redirect(redirect_url);
}
}
next();//触发下一个任务
}
},
readCache: function(req,res,next){
//====so we can use it in request
req.API=api.bind(req);
if(util.is_assets(req.url) || settings.preview){
return next(); // 资源不需要经过此中间件, 预览服务器也不使用缓存
}
var not_reg = new RegExp('(404|500|502)','g'),
not_static = not_reg.test(req.url); //404 page not static
req.update_no = req.query['cache']==="no";
req.not_static = not_static; //不静态化,但缓存
//==== cache=update 更新数据以及页面缓存,cache=no 重新获取数据,但不跟新页面缓存
req.update_cache = (req.query['cache']==='update'||req.update_no);
var has_pwd = req.cookies['www-cache']==='official'||req.query['www-cache']==='official';
if(settings.env != 'production') { has_pwd = true; } //尽在线上环境检查
if(settings.env==='production' && !has_pwd){
req.update_cache = false; //如果 是线上环境,只有cookie中带有指定参数的请求,才能使用 update
}
var update = !settings['page_cache'] || req.update_cache,
cache_key = "ssi:" + req.origin_url;
if(cache_key.has('?') && !req.query['page']){
cache_key = cache_key.substring(0,cache_key.indexOf('?'));
}
req.cache_key = cache_key;
if(update || util.is_assets(req.url)){
return next(); //绕过缓存,触发下一个任务
}
//====== 线上环境 使用 ssi,进行缓存解析
Cache.get(cache_key, function(err,cache) {
if (err) console.log(err);
if (!err && cache) {
req.API.Builder.render_ssi({
html:cache,
getValue:function(key,cb){
Cache.get('/'+key,cb);
}
},function(err,html){
sendCache(cache_key,html);
});
}else{
//=====如果页面缓存不存在,则更新所有缓存
req.update_cache = true;
next(); //触发下一个任务
}
});//end of read cache
//======= 发送页面缓存
function sendCache(cache_key,html){
console.log('-----get page from cache----');
console.log('url----->'+cache_key);
if(not_reg.test(req.url)){
res.send(RegExp.$1,html);
}else{
res.send(200,html);
}
req = res = null;//reset empty
}
},
helper:function(req,res,next){
if(util.is_assets(req.url)){
return next(); // 资源不需要经过此中间件
}
var locale = req.locale_key||req.locale,
API = req.API;
req.locals = {}; //partial local variable
req.page_meta = {}; //清空当前页面的meta
req.product = {};
req = helper.bind(req);
res.locals=_.assign(res.locals,req.helper);
req.set_page('');//清空当前页面的值
req.I18n.locale = locale;
global.helper = helper.helper; //reset global helper
req.csrf_key = HttpHelper.csrf_key;
res.render_view=function(view,data,callback){
var root = process.cwd(),
file_path = view;
if(!file_path.has('.jade')) file_path +=".jade";
//=====如果是mobile,优先使用mobile文件夹
if(req.use_mobile && fs.existsSync(root+"/views/mobile/"+file_path)){
view = "mobile/"+view;
}
//======调用翻译并生成静态文件
HttpHelper.translate_render(req,res,{
view: view,
locale: locale,
data: data,
dir: settings.view_src||'views' //'build/views'
},function(html){
//==== 执行静态化过程, 并进行 redis 缓存
HttpHelper.static_and_cache(req.cache_key,html,req);
if(_.isFunction(callback)){
callback();
}
});// end render
};//end of render_view
res.redirect_v1=function(from){
if(res && !res.is_redirect){
res.is_redirect = true;
var protocol = req.x_protocol ; //protocol by x-forwarded-proto
var origin = settings.remote[settings.env].origin.replace('http',protocol),
redirect_url=origin + req.origin_url.replace(/(\?|&)?www=v1/g,'');
redirect_url += redirect_url.has('?') ?"&www=v1":'?www=v1';
if(!from) from = ""; //标示跳转来源
if(redirect_url.has('change_country')){
redirect_url += ("&referer=" + (req.headers["referer"]||(origin+"/user")));
}
//===== 如果,跳转到旧官网,去掉路径中的 mobile
//if(req.use_mobile){
redirect_url = redirect_url.replace('mobile/','');
//}
//======= 抓取并缓存产品页面
if(req.is_page('product') || req.is_page('products')){
console.log("[:" + from + '] fetch and cache to------->');
console.log(redirect_url);
API.request('get',redirect_url,{
cache_key:req.url
},function(err,resp){
resp = resp||{};
var html = resp.data||resp;
if(err || resp.status!=200 || html.has('<h1>404</h1>')) {
return res.redirect_404(req.url);
}
res.send(200,html);
HttpHelper.static_and_cache(req.cache_key,html,req,'v1');
req = res = null;
},'string');
}else{
//======== 除产品外的,404链接,都跳转到旧官网
console.log("[:" + from + '] redirect to------->');
console.log(redirect_url);
res.redirect(302,redirect_url);
req = res = null;
}
}
//proxy.on('error', function(e) {
// console.log('proxy error');
//});
//return proxy.web(req,res,{ target: redirect_url});
};
res.redirect_err=function(from_url, status){
if(!req || !res) return;
var from = req.origin_url.replace(/^\//,'');
if(from.has('?')){
from = from.substring(0,from.indexOf('?'))
}
status = status ? status : '404';
res.redirect(302,'/' + status + '?from='+(from_url||from));
req = res = null;
};
res.redirect_404=function(from_url){
res.redirect_err(from_url, 404);
};
res.redirect_500=function(from_url){
res.redirect_err(from_url, 500);
};
res.redirect_502=function(from_url){
res.redirect_err(from_url, 502);
};
res.success=function(status,data,extra){
res.json(API.result(true,status,data,extra));
};
res.failure=function(status,data,extra){
res.json(API.result(false,status,data,extra));
};
//===== 检查参数错误
try{
var decode_url = decodeURIComponent(req.originalUrl);
}catch(ex){
Monitor.error('param error---------->' + req.originalUrl);
console.log(ex);
return res.redirect_404('http');
}
next();//触发下一个任务
},
redirect:function(req,res,next){
if(res.redirect_v1){
res.redirect_v1('config/http');
}
next();
},
/***************************************************************************
* *
* The order in which middleware should be run for HTTP request. (the Sails *
* router is invoked by the "router" middleware below.) *
* *
***************************************************************************/
order: [
'startRequestTimer',
'myRequestLogger',
'process_url',
'cookieParser',
'i18n_locale',
'ip_locale',
'is_mobile',
'readCache',
'ddos',
'session',
'bodyParser',
'handleBodyParserError',
'compress',
'methodOverride',
'poweredBy',
'$custom',
'helper',
'csrf',
'router',
'www',
'favicon',
'redirect',
'404',
'500'
],
/****************************************************************************
* *
* Example custom middleware; logs each request to the console. *
* *
****************************************************************************/
xframe: require('lusca').xframe('SAMEORIGIN'),
myRequestLogger: function (req, res, next) {
if(!util.is_assets(req.url)){
console.log('');//换行
console.log('');//换行
}
console.log("Requested :: ", req.method, req.url);
return next();
},
ddos: function(req,res,next){
if(req.method.toUpperCase() == "POST"){
req.ip = req.ip_from;
return Ddos(req,res,next);
}
return next();
},
csrf:function (req,res,next) {
if(util.is_assets(req.url)){ return next(); }
if(req.method=='POST'){
HttpHelper.disableCsrf(req); //当前请求是否需要 csrf 验证
if(!req.session['csrf_token_from']){
var csrf_sec = req.cookies['www_csrf']||'';
req.session['csrfSecret'] = util.RC4.unlock(req.csrf_key,csrf_sec);
}
}
return next();
}
/***************************************************************************
* *
* The body parser that will handle incoming multipart HTTP requests. By *
* default as of v0.10, Sails uses *
* [skipper](http://github.com/balderdashy/skipper). See *
* http://www.senchalabs.org/connect/multipart.html for other options. *
* *
***************************************************************************/
// bodyParser: require('skipper')
}
/***************************************************************************
* *
* The number of seconds to cache flat files on disk being served by *
* Express static middleware (by default, these files are in `.tmp/public`) *
* *
* The HTTP static cache is only active in a 'production' environment, *
* since that's the only time Express will cache flat-files. *
* *
***************************************************************************/
// cache: 31557600000
};
var HttpHelper={
csrf_key: '07bf4fbe48995ee63f41cd68c58d40dc',
translate_render: function(req,res,param,callback){
var locals=_.assign({},res.locals,param.data),
trans_params={
dir: param.dir,
view: param.view,
data: locals,
req: req,
locale: param.locale,
update_cache: req.update_cache
};
var env = req.query.env||settings.env,
API = req.API,
locale = param.locale,
view = param.view;
req.locale=req.locale_key||locale;
settings.env = env; //环境配置
settings.origin = settings.official[env].origin;
API.I18n.translate(trans_params,function(err,trans_data,render){
if(err){
if(err.status==404){
return res.redirect_v1();
}else if(err.status == 502){
return res.redirect_500();
}
return res.send(500,'Server error,please retry later.');
}
if(typeof callback!=='function'){
callback=function(){};
}
if(typeof render==='function'){
try{
var html=render(locals);
}catch(err){
Monitor.error('jade compile error --------->');
console.log(err);
return res.redirect_500();
}
var origin_url=req.origin_url,
url=/\/$/.test(origin_url) ? origin_url+"index" : origin_url;
if(url.has('?')){
url=url.substring(0,url.indexOf('?'))
}
//======build by query, build会重新执行编译过程,一般不需要
if(req.query.build){
var ssi = req.query.ssi; //server side include
API.Builder.build({ locale:locale, html:html,view:view,url:url,env:env,ssi:ssi},
function(err,rest){
res.json(rest);
req = res = null;
});
}else{ //if not build or update
res.send(html);
req = res = null;
}
return callback(html);
}else{
res.view(view,data);
req = res = null;
return callback();
}
});
},
static_and_cache: function(key,html,req,from){
var cache_key = key,
static_key = cache_key,
THREE_DAY = 3 * 24 * 60 * 60,
ONE_WEEK = 7 * 24 * 60 * 60,
API = req.API;
//====== update no 代表 preview,不更新缓存,也不重新静态化,以保持应用的稳定性
if(settings['page_cache'] && !req.update_no){
if(from=='v1' || settings.env!='production'){
//======= 从旧官网拉取的页面直接缓存
Cache.set(cache_key,html);
Cache.expire(cache_key,THREE_DAY);
}else{
//======= 将html进行 ssi 分割后缓存
API.Builder.ssi({
html:html,
cache:function(key,value){
Cache.set("/"+key,value);
Cache.expire("/"+key,ONE_WEEK);
},
locale:req.locale_key||req.locale,
device:req.device
},function(err,rest){
Cache.set(cache_key,rest.html);
Cache.expire(cache_key,THREE_DAY);
});
}
}
if(settings['page_static'] && !req.update_no && !req.not_static){
if(static_key=='/' || static_key=='ssi:/') static_key='index';
static_key = static_key.replace(/(^\/|\/$|^ssi:\/)/g,'')+".html";
process.nextTick(function(){
API.Builder.static({
key:static_key,
html:html,
ssi: !(from == 'v1'),
locale:req.locale_key||req.locale,
device:req.device
},function(err){});
});
}
},
disableCsrf:function(req){
var pwd = (req.body && req.body['www-cache'])
|| (req.params && req.params['www-cache'])
|| (req.query && req.query['www-cache']);
sails.config.csrf.routesDisabled = ""; //reset
if(pwd == 'official'){
sails.config.csrf.routesDisabled = req.path;
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment