Created
January 24, 2014 17:23
-
-
Save williamstein/8601924 to your computer and use it in GitHub Desktop.
This file contains 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
httpProxy = require('http-proxy') | |
init_http_proxy_server = () => | |
_remember_me_check_for_write_access_to_project = (opts) -> | |
opts = defaults opts, | |
project_id : required | |
remember_me : required | |
cb : required # cb(err, has_access) | |
account_id = undefined | |
has_write_access = false | |
async.series([ | |
(cb) -> | |
x = opts.remember_me.split('$') | |
database.key_value_store(name: 'remember_me').get | |
key : generate_hash(x[0], x[1], x[2], x[3]) | |
cb : (err, signed_in_mesg) => | |
account_id = signed_in_mesg?.account_id | |
if err or not account_id? | |
cb('unable to get remember_me cookie from db -- cookie invalid'); return | |
cb() | |
(cb) -> | |
user_has_write_access_to_project | |
project_id : opts.project_id | |
account_id : account_id | |
cb : (err, result) => | |
if err | |
cb(err) | |
else if not result | |
cb("User does not have write access to project.") | |
else | |
has_write_access = true | |
cb() | |
], (err) -> | |
opts.cb(err, has_write_access) | |
) | |
_remember_me_cache = {} | |
remember_me_check_for_write_access_to_project = (opts) -> | |
opts = defaults opts, | |
project_id : required | |
remember_me : required | |
cb : required # cb(err, has_access) | |
key = opts.project_id + opts.remember_me | |
has_write_access = _remember_me_cache[key] | |
if has_write_access? | |
opts.cb(false, has_write_access) | |
return | |
# get the answer, cache it, return answer | |
_remember_me_check_for_write_access_to_project | |
project_id : opts.project_id | |
remember_me : opts.remember_me | |
cb : (err, has_write_access) -> | |
# if cache gets huge for some *weird* reason (should never happen under normal conditions) just reset it to avoid any possibility of DOS-->RAM crash attach | |
if misc.len(_remember_me_cache) >= 100000 | |
_remember_me_cache = {} | |
_remember_me_cache[key] = has_write_access | |
# Set a ttl time bomb on this cache entry. The idea is to keep the cache not too big, | |
# but also if the user is suddenly granted permission to the project, this should be | |
# reflected within a few seconds. | |
f = () -> | |
delete _remember_me_cache[key] | |
if has_write_access | |
setTimeout(f, 1000*60*5) # write access lasts 5 minutes (i.e., if you revoke privs to a user they could still hit the port for 5 minutes) | |
else | |
setTimeout(f, 1000*15) # not having write access lasts 15 seconds | |
opts.cb(err, has_write_access) | |
_target_cache = {} | |
target = (remember_me, url, cb) -> | |
v = url.split('/') | |
project_id = v[1] | |
type = v[2] # 'port' or 'raw' | |
key = remember_me + project_id + type | |
if type == 'port' | |
key += v[3] | |
t = _target_cache[key] | |
if t? | |
cb(false, t) | |
return | |
tm = misc.walltime() | |
winston.debug("target: setting up proxy: #{v}") | |
host = undefined | |
port = undefined | |
async.series([ | |
(cb) -> | |
if not remember_me? | |
# remember_me = undefined means "allow"; this is used for the websocket upgrade. | |
cb(); return | |
remember_me_check_for_write_access_to_project | |
project_id : project_id | |
remember_me : remember_me | |
cb : (err, has_access) -> | |
if err | |
cb(err) | |
else if not has_access | |
cb("user does not have write access to this project") | |
else | |
cb() | |
(cb) -> | |
database.get_project_location | |
project_id : project_id | |
allow_cache : false | |
cb : (err, _location) -> | |
if err | |
cb(err) | |
else | |
if _location? | |
host = _location.host | |
cb() | |
(cb) -> | |
if host? | |
cb() | |
else | |
storage.open_project_somewhere | |
project_id : project_id | |
base_url : program.base_url | |
cb : (err, _host) -> | |
host = _host | |
cb(err) | |
(cb) -> | |
# determine the port | |
if type == 'port' | |
port = parseInt(v[3]) | |
cb() | |
else if type == 'raw' | |
new_local_hub | |
project_id : project_id | |
cb : (err, local_hub) -> | |
if err | |
cb(err) | |
else | |
# TODO Optimization: getting the status is slow (half second?), so | |
# we cache this for 15 seconds below; caching longer | |
# could cause trouble due to project restarts, but we'll | |
# have to look into that for speed (and maybe use the database | |
# to better track project restarts). | |
local_hub._get_local_hub_status (err, status) -> | |
if err | |
cb(err) | |
else | |
port = status['raw.port'] | |
cb() | |
else | |
cb("unknown url type -- #{type}") | |
], (err) -> | |
winston.debug("target: setup proxy; time=#{misc.walltime(tm)} seconds") | |
if err | |
cb(err) | |
else | |
t = {host:host, port:port} | |
_target_cache[key] = t | |
# Set a ttl time bomb on this cache entry. The idea is to keep the cache not too big, | |
# but also if the user is suddenly granted permission to the project, or the project server | |
# is restarted, this should be reflected. Since there are dozens (at least) of hubs, | |
# and any could cause a project restart at any time, we just timeout this info after | |
# a few seconds. This helps enormously when there is a burst of requests. | |
setTimeout((()->delete _target_cache[key]), 1000*30) | |
cb(false, t) | |
) | |
#proxy = httpProxy.createProxyServer(ws:true) | |
http_proxy_server = http.createServer (req, res) -> | |
req_url = req.url.slice(program.base_url.length) # strip base_url for purposes of determining project location/permissions | |
if req_url == "/alive" | |
res.end('') | |
return | |
#buffer = httpProxy.buffer(req) # see http://stackoverflow.com/questions/11672294/invoking-an-asynchronous-method-inside-a-middleware-in-node-http-proxy | |
cookies = new Cookies(req, res) | |
remember_me = cookies.get(program.base_url + 'remember_me') | |
if not remember_me? | |
res.writeHead(500, {'Content-Type':'text/html'}) | |
res.end("Please login to <a target='_blank' href='https://cloud.sagemath.com'>https://cloud.sagemath.com</a> with cookies enabled, then refresh this page.") | |
return | |
target remember_me, req_url, (err, location) -> | |
if err | |
winston.debug("proxy denied -- #{err}") | |
res.writeHead(500, {'Content-Type':'text/html'}) | |
res.end("Access denied. Please login to <a target='_blank' href='https://cloud.sagemath.com'>https://cloud.sagemath.com</a> as a user with access to this project, then refresh this page.") | |
else | |
proxy = httpProxy.createProxyServer(ws:false, target:"http://#{location.host}:#{location.port}", timeout:0) | |
proxy.web(req, res) | |
http_proxy_server.listen(program.proxy_port, program.host) | |
_ws_proxy_servers = {} | |
http_proxy_server.on 'upgrade', (req, socket, head) -> | |
req_url = req.url.slice(program.base_url.length) # strip base_url for purposes of determining project location/permissions | |
target undefined, req_url, (err, location) -> | |
if err | |
winston.debug("websocket upgrade error -- this shouldn't happen since upgrade would only happen after normal thing *worked*. #{err}") | |
else | |
winston.debug("websocket upgrade -- ws://#{location.host}:#{location.port}") | |
t = "ws://#{location.host}:#{location.port}" | |
proxy = _ws_proxy_servers[t] | |
if not proxy? | |
winston.debug("websocket upgrade: not using cache") | |
proxy = httpProxy.createProxyServer(ws:true, target:t, timeout:0) | |
_ws_proxy_servers[t] = proxy | |
else | |
winston.debug("websocket upgrade: using cache") | |
proxy.ws(req, socket, head) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment