Created
June 2, 2015 12:10
-
-
Save guileen/65b14d06a8ae4c46d8f0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 定制指南: | |
* getKeyInfoOfRequest : 获取缓存key的方法 | |
* setCache | |
* getCache | |
*/ | |
var crypto = require('crypto') | |
var redis = require('redis') | |
var parseUrl = require('url').parse | |
var getRawBody = require('raw-body') | |
var typer = require('media-typer') | |
var cclog = require('cclog') | |
var parseQuery = require('querystring').parse | |
var http = require('http') | |
var upstreamPort = 4000 | |
var upstreamHost = '127.0.0.1' | |
var redisClient = redis.createClient() | |
// demo server | |
http.createServer(function(req, res) { | |
console.log('upstream on request', req.method, req.url) | |
var s = new Array( 10 ).join('repeat repeat') | |
setTimeout(function() { | |
res.end(req.method + req.url + '\n' + s) | |
}, 1000) | |
}).listen(4000) | |
// real proxy server | |
http.createServer(function(req, res) { | |
var ip = req.connection.remoteAddress; | |
var urlInfo = parseUrl(req.url, true) | |
req.path = urlInfo.path | |
req.query = urlInfo.query | |
if(!isCacheable(req)) { | |
pipeUpstream(req, res) | |
} else { | |
cacheUpstream(req, res) | |
} | |
}).listen(3000) | |
console.log('server listen at 3000') | |
// if you want to redefine cache rule | |
function isCacheable(req) { | |
return req.method == 'GET' || req.method == 'HEAD' | |
} | |
function getKeyAndTTL(req, body) { | |
return { | |
key: hash(req.url), | |
ttl: 3 * 3600 | |
} | |
} | |
// if you want to use another cache | |
function setCache(key, obj, ttl, callback) { | |
var hkey = 'cache:' + key | |
redisClient.multi().hset(hkey, '_', JSON.stringify(obj)).expire(hkey, ttl).exec(callback) | |
} | |
function getCache(key, callback) { | |
var hkey = 'cache:' + key | |
redisClient.hget(hkey, '_', function(err, result) { | |
if(err) return callback(err) | |
callback(null, JSON.parse(result)) | |
}) | |
} | |
function hash(str) { | |
return crypto.createHash('sha1').update(str).digest('hex') | |
} | |
function pipeUpstream(req, res) { | |
console.log('skip', req.method, req.url) | |
var proxyReq = http.request({ | |
port: upstreamPort, | |
host: upstreamHost | |
}, function(proxyRes) { | |
proxyRes.pipe(res) | |
}) | |
req.pipe(proxyReq) | |
} | |
function cacheUpstream(req, res) { | |
getKeyInfoOfRequest(req, res, function(err, keyInfo) { | |
if(err) { | |
// ignore | |
} | |
if(keyInfo && keyInfo.key) { | |
loadFromCache(keyInfo, req, res) | |
} else { | |
pipeUpstream(req, res) | |
} | |
}) | |
} | |
// cb(err, {key:'', ttl:'', body: rawbody}) | |
function getKeyInfoOfRequest(req, res, callback) { | |
var contentType = req.headers['content-type'] || '' | |
var encoding = contentType && typer.parse(contentType).parameters.charset | |
getRawBody(req, { | |
length: req.headers['content-length'], | |
limit: '1mb', | |
encoding: encoding | |
}, function (err, string) { | |
if (err) return next(err) | |
var body | |
if(contentType.indexOf('json') !== -1) { | |
body = JSON.parse(string) | |
} else if(contentType.indexOf('urlencoded') !== -1) { | |
body = parseQuery(string) | |
} | |
var keyInfo = getKeyAndTTL(req, body) | |
keyInfo.body = string | |
keyInfo.encoding = encoding | |
callback(null, keyInfo) | |
}) | |
} | |
function responesObj(req, res, obj) { | |
console.log('hit', req.method, req.url) | |
obj.headers['X-Cache'] = 'HIT' | |
res.writeHead(obj.statusCode, obj.headers) | |
res.end(obj.body) | |
} | |
var cacheableCodes = [200, 302, 404] | |
function loadUpstreamAndSave(keyInfo, req, res) { | |
console.log('miss', req.method, req.url) | |
var proxyReq = http.request({ | |
port: upstreamPort, | |
host: upstreamHost, | |
}, function(proxyRes) { | |
var statusCode = proxyRes.statusCode | |
// clone headers | |
var headers = JSON.parse(JSON.stringify(proxyRes.headers)) | |
headers['X-Cache'] = 'MISS' | |
res.writeHead(statusCode, headers) | |
var cacheable = cacheableCodes.indexOf(proxyRes.statusCode) !== -1 | |
var chunks = cacheable ? [] : null | |
proxyRes.on('data', function(chunk) { | |
cacheable && chunks.push(chunk) | |
res.write(chunk) | |
}) | |
proxyRes.on('end', function() { | |
res.end() | |
if(cacheable) { | |
var obj = { | |
statusCode: statusCode, | |
headers: proxyRes.headers, | |
body: chunks.join('') | |
} | |
setCache(keyInfo.key, obj, keyInfo.ttl, cclog.ifError) | |
} | |
}) | |
}) | |
if(keyInfo.body) { | |
proxyReq.write(keyInfo.body, keyInfo.encoding) | |
} | |
proxyReq.end() | |
} | |
function loadFromCache(keyInfo, req, res) { | |
var key = keyInfo.key | |
getCache(key, function(err, obj) { | |
if(!err && obj) { | |
// hit | |
return responesObj(req, res, obj) | |
} | |
// not hit | |
loadUpstreamAndSave(keyInfo, req, res) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment