Created
April 11, 2012 04:36
-
-
Save shepherdwind/2356909 to your computer and use it in GitHub Desktop.
proxy for node
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
#!/usr/bin/env node | |
/** | |
* Created by [email protected] | |
* | |
* 使用方式,依赖于node | |
* 1. 下载node.js, http://nodejs.org/#download | |
* 2. 在命令行下执行: | |
* node proxy.js | |
* | |
* 静态文件代理工具,基于node实现,实现原理,把a.tbcdn.cn通过host设置,转到本地 | |
* node所创建的服务器中,由node来处理请求。比如访问浏览器请求url地址: | |
* http://a.tbcdn.cn/??a/a.css,p/global/global-min.css,b/b.css, | |
* 当前目录结构如下 | |
* . | |
* |____a | |
* | |____a.css | |
* |____b | |
* | |____c.css | |
* |____p | |
* | |____global | |
* | | |____global-min.css | |
* |____proxy.js | |
* | |
* 那么,通过proxy.js,a/a.css, p/global/global-min.css会请求本地的文件,然后返 | |
* 回给浏览器,而b/b.css在本地目录中没有,则会从线上取文件。 | |
* 通过host作用,访问a.tbcdn.cn可以进行配置,是否实行代理。 | |
**/ | |
"use strict"; | |
var assert = require('assert'), | |
dns = require('dns'), | |
fs = require('fs'), | |
http = require('http'), | |
querystring = require('querystring'); | |
var USE_LOCAL = true, | |
USE_PRE = false, | |
IP_PRE = '110.75.14.33', | |
IP_PUB, | |
PUB_SRV = 'assets.gslb.taobao.com', | |
MIME = { | |
js: 'application/x-javascript', | |
css: 'text/css', | |
txt: 'text/plain', | |
png: 'image/png', | |
gif: 'image/gif', | |
jpg: 'image/jpeg', | |
swf: 'application/x-shockwave-flash' | |
}; | |
function getIp(cb) {//{{{ | |
dns.resolve4(PUB_SRV, function (err, addresses) { | |
if (err) { | |
assert(!err, 'Can\'t connect to ' + PUB_SRV); | |
} | |
IP_PUB = addresses[0]; | |
console.log('[status] pub server: ' + IP_PUB); | |
cb(); | |
}); | |
}//}}} | |
function setStatus(req, res) {//{{{ | |
req.setEncoding('utf8'); | |
var formData = ''; | |
req.on('data', function (data) { | |
formData += data; | |
}); | |
req.on('end', function (data) { | |
console.log('[status]' + formData); | |
formData = querystring.parse(formData); | |
USE_LOCAL = formData.on === 'true'; | |
USE_PRE = formData.is_pre === 'true'; | |
getStatus(req, res); | |
}); | |
}//}}} | |
function getStatus(req, res) {//{{{ | |
res.writeHead(200, {'Content-Type': 'text/html'}); | |
res.end('<!doctype html><meta charset="utf-8"><title>CDN Proxy</title>' + | |
'<form method="post">' + | |
'<div class="control-group">' + | |
'<label class="control-label">本地代理开关</label>' + | |
'<div class="controls">' + | |
'<label class="radio"><input type="radio" name="on" value="true"' + (USE_LOCAL ? ' checked' : '') + '>开</label>' + | |
'<label class="radio"><input type="radio" name="on" value="false"' + (USE_LOCAL ? '' : ' checked') + '>关</label>' + | |
'</div>' + | |
'</div>' + | |
'<div class="control-group">' + | |
'<label class="control-label">远程文件地址:预发/线上</label>' + | |
'<div class="controls">' + | |
'<label class="radio"><input type="radio" name="is_pre" value="true"' + (USE_PRE ? ' checked' : '') + '>预发</label>' + | |
'<label class="radio"><input type="radio" name="is_pre" value="false"' + (USE_PRE ? '' : ' checked') + '>线上</label>' + | |
'</div>' + | |
'</div>' + | |
'<button type="submit">提交</button>' + | |
'</form>'); | |
}//}}} | |
function proxy(req, res) {//{{{ | |
var files = parse(req.url); | |
console.log('[list] ' + files); | |
getFiles(files, | |
function gotAll(fileContents) { | |
// 不能直接取1 | |
// file: p/global/1.0/global-min.css 会出现bug | |
var suffix = files[0].split('.').pop(); | |
res.writeHead(200, {'Content-Type': MIME[suffix] || MIME.txt}); | |
fileContents.forEach(function (file) { | |
res.write(file); | |
}); | |
res.end(); | |
}, | |
function doh(err) { | |
res.writeHead(200, {'Content-Type': MIME.txt}); | |
res.end(err.message); | |
} | |
); | |
}//}}} | |
function parse(url) {//{{{ | |
var ret = [], | |
combo = url.indexOf('??'), | |
base, files; | |
if (-1 !== combo) { | |
base = url.slice(0, combo); | |
files = url.slice(combo + 2); | |
files = files.split('?')[0]; | |
files = files.split('#')[0]; | |
files = files.split(','); | |
files.forEach(function (file) { | |
ret.push(base + file); | |
}); | |
} else { | |
url = url.split('?')[0]; | |
url = url.split('#')[0]; | |
ret.push(url); | |
} | |
return ret; | |
}//}}} | |
function getFiles(files, success, fail) {//{{{ | |
var len = files.length, | |
rets = []; | |
function cb(index) { | |
return function set(ret) { | |
if (ret instanceof Error) { | |
fail(ret); | |
} else { | |
rets[index] = ret; | |
var k = len, | |
finished = true; | |
while (finished && k--) { | |
finished = finished && undefined !== rets[k]; | |
} | |
if (finished) { | |
success(rets); | |
} | |
} | |
}; | |
} | |
files.forEach(function (file, index) { | |
getFile(file, cb(index)); | |
}); | |
}//}}} | |
function getFile(file, cb) {//{{{ | |
var fileContent = null; | |
if (USE_LOCAL) { | |
fileContent = getLocal(file); | |
} | |
if (null !== fileContent) { | |
cb(fileContent); | |
} else { | |
getRemote(file, cb); | |
} | |
}//}}} | |
function getRemote(file, cb) {//{{{ | |
http.get({ | |
headers: { | |
host: 'a.tbcdn.cn' | |
}, | |
host: USE_PRE ? IP_PRE : IP_PUB, | |
port: 80, | |
path: file | |
}, function (res) { | |
var fileBuffer, | |
buffers = [], | |
size = 0; | |
res.on('data', function (data) { | |
size += data.length; | |
buffers.push(data); | |
}); | |
res.on('end', function () { | |
var p = 0; | |
fileBuffer = new Buffer(size); | |
buffers.forEach(function (buffer) { | |
var len = buffer.length; | |
buffer.copy(fileBuffer, p, 0, len); | |
p += len; | |
}); | |
cb(fileBuffer); | |
console.log('[remote] got ' + file); | |
}); | |
}).on('error', function (e) { | |
cb(new Error('File not found: ' + file)); | |
console.log('[remote] not got ' + file); | |
}); | |
}//}}} | |
function getLocal(file) {//{{{ | |
var ret; | |
try { | |
ret = fs.readFileSync('.' + file); | |
console.log('[local] found ' + file); | |
} catch (e) { | |
ret = null; | |
console.log('[local] not found ' + file); | |
} | |
return ret; | |
}//}}} | |
getIp(function () {//{{{ | |
http.createServer(function (req, res) { | |
if (req.method === 'POST') { | |
setStatus(req, res); | |
} else if (req.url === '/') { | |
getStatus(req, res); | |
} else { | |
proxy(req, res); | |
} | |
}).listen(80); | |
});//}}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment