Files
jupyterhub/multiuser.py
2014-06-12 16:22:06 -07:00

187 lines
5.1 KiB
Python

#!/usr/bin/env python
import socket
import sys
import uuid
from subprocess import Popen
import json
import requests
import time
import tornado.httpserver
import tornado.ioloop
import tornado.options
from tornado.log import app_log
from tornado.escape import url_escape
from tornado.httputil import url_concat
from tornado.web import RequestHandler, Application
from tornado import web
from tornado.options import define, options
def random_port():
sock = socket.socket()
sock.bind(('', 0))
port = sock.getsockname()[1]
sock.close()
return port
define("port", default=8001, help="run on the given port", type=int)
class SingleUserManager(object):
routes_t = 'http://localhost:8000/api/routes{uri}'
single_user_t = 'http://localhost:{port}'
def __init__(self):
self.processes = {}
self.ports = {}
def _wait_for_port(self, port, timeout=2):
tic = time.time()
while time.time() - tic < timeout:
try:
socket.create_connection(('localhost', port))
except socket.error:
time.sleep(0.1)
else:
break
def spawn(self, user):
assert user not in self.processes
port = random_port()
self.processes[user] = Popen(
[sys.executable, 'singleuser.py', '--port=%i' % port, '--user=%s' % user])
self.ports[user] = port
r = requests.post(
self.routes_t.format(uri=u'/user/%s' % user),
data=json.dumps(dict(
target=self.single_user_t.format(port=port),
user=user,
)),
)
self._wait_for_port(port)
r.raise_for_status()
print("spawn done")
def exists(self, user):
if user in self.processes:
if self.processes[user].poll() is None:
return True
else:
self.processes.pop(user)
self.ports.pop(user)
return False
def get(self, user):
"""ensure process exists and return its port"""
if not self.exists(user):
self.spawn(user)
return self.ports[user]
def shutdown(self, user):
assert user in self.processes
port = self.ports[user]
self.processes[user].terminate()
r = requests.delete(self.routes_url,
data=json.dumps(user=user, port=port),
)
r.raise_for_status()
class BaseHandler(RequestHandler):
cookie_name = 'multiusertest'
def get_current_user(self):
user = self.get_cookie(self.cookie_name, '')
if user:
return user
@property
def user_manager(self):
return self.settings['user_manager']
def clear_login_cookie(self):
self.clear_cookie(self.cookie_name)
class MainHandler(BaseHandler):
@web.authenticated
def get(self):
self.redirect("/user/%s" % self.get_current_user())
class UserHandler(BaseHandler):
@web.authenticated
def get(self, user):
self.write("multi-user at single-user url: %s" % user)
if self.get_current_user() == user:
self.user_manager.spawn(user)
self.redirect('')
else:
self.clear_login_cookie()
self.redirect(url_concat(self.settings['login_url'], {
'next' : '/user/%s' % user
}))
class LogoutHandler(BaseHandler):
def get(self):
self.clear_login_cookie()
self.write("logged out")
class LoginHandler(BaseHandler):
def _render(self, message=None, user=None):
self.render('login.html',
next=url_escape(self.get_argument('next', default='')),
user=user,
message=message,
)
def get(self):
if self.get_current_user():
self.redirect(self.get_argument('next', default='/'))
else:
user = self.get_argument('user', default='')
self._render(user=user)
def post(self):
user = self.get_argument('user', default='')
pwd = self.get_argument('password', default=u'')
next_url = self.get_argument('next', default='') or '/user/%s' % user
if user and pwd == 'password':
self.set_cookie(self.cookie_name, user)
else:
self._render(
message={'error': 'Invalid username or password'},
user=user,
)
return
self.redirect(next_url)
def main():
tornado.options.parse_command_line()
application = Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
# (r"/shutdown/([^/]+)", ShutdownHandler),
# (r"/start/([^/]+)", StartHandler),
(r"/user/([^/]+)/?.*", UserHandler),
],
user_manager=SingleUserManager(),
cookie_secret='super secret',
login_url='/login',
)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
proxy = Popen(["node", "proxy.js"])
try:
tornado.ioloop.IOLoop.instance().start()
finally:
proxy.terminate()
if __name__ == "__main__":
main()