mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
187 lines
5.1 KiB
Python
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()
|