Skip to content

Instantly share code, notes, and snippets.

@guileen
Created June 2, 2015 12:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guileen/65b14d06a8ae4c46d8f0 to your computer and use it in GitHub Desktop.
Save guileen/65b14d06a8ae4c46d8f0 to your computer and use it in GitHub Desktop.
/**
* 定制指南:
* 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