subdomain dispatcher
This commit is contained in:
		
							parent
							
								
									27ced9d90e
								
							
						
					
					
						commit
						2906b79dfc
					
				
					 4 changed files with 215 additions and 185 deletions
				
			
		
							
								
								
									
										33
									
								
								app.py
									
										
									
									
									
								
							
							
						
						
									
										33
									
								
								app.py
									
										
									
									
									
								
							|  | @ -1,8 +1,35 @@ | ||||||
| from gevent import monkey, pywsgi | from gevent import monkey, pywsgi | ||||||
| monkey.patch_all() | monkey.patch_all() | ||||||
| import logging | from realms import create_app, config | ||||||
| from realms import app, config | from threading import Lock | ||||||
|  | 
 | ||||||
|  | class SubdomainDispatcher(object): | ||||||
|  | 
 | ||||||
|  |     def __init__(self, domain, create_app): | ||||||
|  |         self.domain = domain | ||||||
|  |         self.create_app = create_app | ||||||
|  |         self.lock = Lock() | ||||||
|  |         self.instances = {} | ||||||
|  | 
 | ||||||
|  |     def get_application(self, host): | ||||||
|  |         host = host.split(':')[0] | ||||||
|  |         assert host.endswith(self.domain), 'Configuration error' | ||||||
|  |         subdomain = host[:-len(self.domain)].rstrip('.') | ||||||
|  |         with self.lock: | ||||||
|  |             app = self.instances.get(subdomain) | ||||||
|  |             if app is None: | ||||||
|  |                 app = self.create_app(subdomain) | ||||||
|  |                 self.instances[subdomain] = app | ||||||
|  |             return app | ||||||
|  | 
 | ||||||
|  |     def __call__(self, environ, start_response): | ||||||
|  |         app = self.get_application(environ['HTTP_HOST']) | ||||||
|  |         return app(environ, start_response) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def make_app(subdomain): | ||||||
|  |     return create_app(subdomain) | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     app.logger.setLevel(logging.INFO) |     app = SubdomainDispatcher(config.domain, make_app) | ||||||
|     pywsgi.WSGIServer(('', config.port), app).serve_forever() |     pywsgi.WSGIServer(('', config.port), app).serve_forever() | ||||||
|  |  | ||||||
|  | @ -2,19 +2,17 @@ import logging | ||||||
| import os | import os | ||||||
| import time | import time | ||||||
| 
 | 
 | ||||||
| import redis |  | ||||||
| import rethinkdb as rdb |  | ||||||
| from flask import Flask, request, render_template, url_for, redirect, flash, session | from flask import Flask, request, render_template, url_for, redirect, flash, session | ||||||
| from flask.ext.bcrypt import Bcrypt | from flask.ext.bcrypt import Bcrypt | ||||||
| from flask.ext.login import LoginManager, login_user, logout_user | from flask.ext.login import LoginManager, login_user, logout_user | ||||||
| from flask.ext.assets import Environment | from flask.ext.assets import Environment | ||||||
| from recaptcha.client import captcha | from recaptcha.client import captcha | ||||||
| from werkzeug.routing import BaseConverter | from werkzeug.routing import BaseConverter | ||||||
| 
 |  | ||||||
| from session import RedisSessionInterface | from session import RedisSessionInterface | ||||||
| import config | import config | ||||||
| from wiki import Wiki | from wiki import Wiki | ||||||
| from util import to_canonical, remove_ext, mkdir_safe, gravatar_url | from util import to_canonical, remove_ext, mkdir_safe, gravatar_url | ||||||
|  | from models import Site, User, CurrentUser | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RegexConverter(BaseConverter): | class RegexConverter(BaseConverter): | ||||||
|  | @ -22,48 +20,6 @@ class RegexConverter(BaseConverter): | ||||||
|         super(RegexConverter, self).__init__(url_map) |         super(RegexConverter, self).__init__(url_map) | ||||||
|         self.regex = items[0] |         self.regex = items[0] | ||||||
| 
 | 
 | ||||||
| app = Flask(__name__) |  | ||||||
| app.config.update(config.flask) |  | ||||||
| app.debug = (config.ENV is not 'PROD') |  | ||||||
| app.secret_key = config.secret_key |  | ||||||
| app.static_path = os.sep + 'static' |  | ||||||
| app.session_interface = RedisSessionInterface() |  | ||||||
| app.url_map.converters['regex'] = RegexConverter |  | ||||||
| 
 |  | ||||||
| bcrypt = Bcrypt(app) |  | ||||||
| 
 |  | ||||||
| login_manager = LoginManager() |  | ||||||
| login_manager.init_app(app) |  | ||||||
| 
 |  | ||||||
| assets = Environment(app) |  | ||||||
| assets.url = app.static_url_path |  | ||||||
| assets.directory = app.static_folder |  | ||||||
| 
 |  | ||||||
| cache = redis.StrictRedis(host=config.cache['host'], port=config.cache['port']) |  | ||||||
| 
 |  | ||||||
| conn = rdb.connect(config.db['host'], config.db['port'], db=config.db['dbname']) |  | ||||||
| 
 |  | ||||||
| if not config.db['dbname'] in rdb.db_list().run(conn) and config.ENV is not 'PROD': |  | ||||||
|     # Create default db and repo |  | ||||||
|     print "Creating DB %s" % config.db['dbname'] |  | ||||||
|     rdb.db_create(config.db['dbname']).run(conn) |  | ||||||
|     for tbl in ['sites', 'users', 'pages']: |  | ||||||
|         rdb.table_create(tbl).run(conn) |  | ||||||
| 
 |  | ||||||
| main_repo_dir = config.repos['main'] |  | ||||||
| repo_dir = config.repos['dir'] |  | ||||||
| 
 |  | ||||||
| # This is down here because of dependencies above |  | ||||||
| from models import Site, User, CurrentUser |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @login_manager.user_loader |  | ||||||
| def load_user(user_id): |  | ||||||
|     return CurrentUser(user_id) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| w = Wiki(main_repo_dir) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def redirect_url(): | def redirect_url(): | ||||||
|     return request.args.get('next') or request.referrer or url_for('index') |     return request.args.get('next') or request.referrer or url_for('index') | ||||||
|  | @ -73,39 +29,69 @@ def validate_captcha(): | ||||||
|     response = captcha.submit( |     response = captcha.submit( | ||||||
|         request.form['recaptcha_challenge_field'], |         request.form['recaptcha_challenge_field'], | ||||||
|         request.form['recaptcha_response_field'], |         request.form['recaptcha_response_field'], | ||||||
|         app.config['RECAPTCHA_PRIVATE_KEY'], |         config.flask['RECAPTCHA_PRIVATE_KEY'], | ||||||
|         request.remote_addr) |         request.remote_addr) | ||||||
|     return response.is_valid |     return response.is_valid | ||||||
| 
 | 
 | ||||||
| @app.template_filter('datetime') | 
 | ||||||
| def _jinja2_filter_datetime(ts): | def create_app(subdomain=None): | ||||||
|  |     app = Flask(__name__) | ||||||
|  |     app.config.update(config.flask) | ||||||
|  |     app.debug = (config.ENV is not 'PROD') | ||||||
|  |     app.secret_key = config.secret_key | ||||||
|  |     app.static_path = os.sep + 'static' | ||||||
|  |     app.session_interface = RedisSessionInterface() | ||||||
|  |     app.url_map.converters['regex'] = RegexConverter | ||||||
|  | 
 | ||||||
|  |     bcrypt = Bcrypt(app) | ||||||
|  | 
 | ||||||
|  |     login_manager = LoginManager() | ||||||
|  |     login_manager.init_app(app) | ||||||
|  | 
 | ||||||
|  |     @login_manager.user_loader | ||||||
|  |     def load_user(user_id): | ||||||
|  |         return CurrentUser(user_id) | ||||||
|  | 
 | ||||||
|  |     assets = Environment(app) | ||||||
|  |     assets.url = app.static_url_path | ||||||
|  |     assets.directory = app.static_folder | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     main_repo_dir = config.repos['main'] | ||||||
|  |     repo_dir = config.repos['dir'] | ||||||
|  | 
 | ||||||
|  |     w = Wiki(main_repo_dir) if not subdomain else Wiki(repo_dir + "/" + subdomain) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     @app.template_filter('datetime') | ||||||
|  |     def _jinja2_filter_datetime(ts): | ||||||
|         return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts)) |         return time.strftime('%b %d, %Y %I:%M %p', time.localtime(ts)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.errorhandler(404) |     @app.errorhandler(404) | ||||||
| def page_not_found(e): |     def page_not_found(e): | ||||||
|         return render_template('errors/404.html'), 404 |         return render_template('errors/404.html'), 404 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.errorhandler(500) |     @app.errorhandler(500) | ||||||
| def page_error(e): |     def page_error(e): | ||||||
|         logging.exception(e) |         logging.exception(e) | ||||||
|         return render_template('errors/500.html'), 500 |         return render_template('errors/500.html'), 500 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/") |     @app.route("/") | ||||||
| def root(): |     def root(): | ||||||
|         return render('home') |         return render('home') | ||||||
|         #return redirect('/home') |         #return redirect('/home') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/account/") |     @app.route("/account/") | ||||||
| def account(): |     def account(): | ||||||
|         return render_template('account/index.html') |         return render_template('account/index.html') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/_new/", methods=['GET', 'POST']) |     @app.route("/_new/", methods=['GET', 'POST']) | ||||||
| def new_wiki(): |     def new_wiki(): | ||||||
|         if request.method == 'POST': |         if request.method == 'POST': | ||||||
|             # TODO validate wiki name |             # TODO validate wiki name | ||||||
|             wiki_name = request.form['name'] |             wiki_name = request.form['name'] | ||||||
|  | @ -120,14 +106,14 @@ def new_wiki(): | ||||||
|             return render_template('_new/index.html') |             return render_template('_new/index.html') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/logout/") |     @app.route("/logout/") | ||||||
| def logout(): |     def logout(): | ||||||
|         logout_user() |         logout_user() | ||||||
|         del session['user'] |         del session['user'] | ||||||
|         return redirect(url_for('root')) |         return redirect(url_for('root')) | ||||||
| 
 | 
 | ||||||
| @app.route("/commit/<sha>/<name>") |     @app.route("/commit/<sha>/<name>") | ||||||
| def commit_sha(name, sha): |     def commit_sha(name, sha): | ||||||
|         cname = to_canonical(name) |         cname = to_canonical(name) | ||||||
| 
 | 
 | ||||||
|         data = w.get_page(cname, sha=sha) |         data = w.get_page(cname, sha=sha) | ||||||
|  | @ -137,14 +123,14 @@ def commit_sha(name, sha): | ||||||
|             return redirect('/create/'+cname) |             return redirect('/create/'+cname) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>") |     @app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>") | ||||||
| def compare(name, fsha, dots, lsha): |     def compare(name, fsha, dots, lsha): | ||||||
|         diff = w.compare(name, fsha, lsha) |         diff = w.compare(name, fsha, lsha) | ||||||
|         return render_template('page/compare.html', name=name, diff=diff) |         return render_template('page/compare.html', name=name, diff=diff) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/register", methods=['GET', 'POST']) |     @app.route("/register", methods=['GET', 'POST']) | ||||||
| def register(): |     def register(): | ||||||
|         if request.method == 'POST': |         if request.method == 'POST': | ||||||
|             user = User() |             user = User() | ||||||
|             if user.get_by_email(request.form['email']): |             if user.get_by_email(request.form['email']): | ||||||
|  | @ -166,8 +152,8 @@ def register(): | ||||||
|             return render_template('account/register.html') |             return render_template('account/register.html') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/login", methods=['GET', 'POST']) |     @app.route("/login", methods=['GET', 'POST']) | ||||||
| def login(): |     def login(): | ||||||
|         if request.method == 'POST': |         if request.method == 'POST': | ||||||
|             if User.auth(request.form['email'], request.form['password']): |             if User.auth(request.form['email'], request.form['password']): | ||||||
|                 return redirect("/") |                 return redirect("/") | ||||||
|  | @ -178,14 +164,14 @@ def login(): | ||||||
|             return render_template('account/login.html') |             return render_template('account/login.html') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/history/<name>") |     @app.route("/history/<name>") | ||||||
| def history(name): |     def history(name): | ||||||
|         history = w.get_history(name) |         history = w.get_history(name) | ||||||
|         return render_template('page/history.html', name=name, history=history) |         return render_template('page/history.html', name=name, history=history) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/edit/<name>", methods=['GET', 'POST']) |     @app.route("/edit/<name>", methods=['GET', 'POST']) | ||||||
| def edit(name): |     def edit(name): | ||||||
|         data = w.get_page(name) |         data = w.get_page(name) | ||||||
|         cname = to_canonical(name) |         cname = to_canonical(name) | ||||||
|         if request.method == 'POST': |         if request.method == 'POST': | ||||||
|  | @ -203,14 +189,14 @@ def edit(name): | ||||||
|                 return redirect('/create/'+cname) |                 return redirect('/create/'+cname) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/delete/<name>", methods=['POST']) |     @app.route("/delete/<name>", methods=['POST']) | ||||||
| def delete(name): |     def delete(name): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/create/", methods=['GET', 'POST']) |     @app.route("/create/", methods=['GET', 'POST']) | ||||||
| @app.route("/create/<name>", methods=['GET', 'POST']) |     @app.route("/create/<name>", methods=['GET', 'POST']) | ||||||
| def create(name=None): |     def create(name=None): | ||||||
|         cname = "" |         cname = "" | ||||||
|         if name: |         if name: | ||||||
|             cname = to_canonical(name) |             cname = to_canonical(name) | ||||||
|  | @ -224,8 +210,8 @@ def create(name=None): | ||||||
|             return render_template('page/edit.html', name=cname, content="") |             return render_template('page/edit.html', name=cname, content="") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @app.route("/<name>") |     @app.route("/<name>") | ||||||
| def render(name): |     def render(name): | ||||||
|         cname = to_canonical(name) |         cname = to_canonical(name) | ||||||
|         if cname != name: |         if cname != name: | ||||||
|             return redirect('/' + cname) |             return redirect('/' + cname) | ||||||
|  | @ -236,4 +222,4 @@ def render(name): | ||||||
|         else: |         else: | ||||||
|             return redirect('/create/'+cname) |             return redirect('/create/'+cname) | ||||||
| 
 | 
 | ||||||
| import ratelimit |     return app | ||||||
|  | @ -1,9 +1,25 @@ | ||||||
| import rethinkdb as rdb | import rethinkdb as rdb | ||||||
|  | import bcrypt | ||||||
|  | import redis | ||||||
| from flask import session | from flask import session | ||||||
| from flask.ext.login import login_user | from flask.ext.login import login_user | ||||||
| from rethinkORM import RethinkModel | from rethinkORM import RethinkModel | ||||||
| from realms import conn, bcrypt | from realms import config | ||||||
| 
 | 
 | ||||||
|  | # Default DB connection | ||||||
|  | conn = rdb.connect(config.db['host'], config.db['port'], db=config.db['dbname']) | ||||||
|  | 
 | ||||||
|  | # Default Cache connection | ||||||
|  | cache = redis.StrictRedis(host=config.cache['host'], port=config.cache['port']) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def init_db(): | ||||||
|  |     if not config.db['dbname'] in rdb.db_list().run(conn) and config.ENV is not 'PROD': | ||||||
|  |         # Create default db and repo | ||||||
|  |         print "Creating DB %s" % config.db['dbname'] | ||||||
|  |         rdb.db_create(config.db['dbname']).run(conn) | ||||||
|  |         for tbl in ['sites', 'users', 'pages']: | ||||||
|  |             rdb.table_create(tbl).run(conn) | ||||||
| 
 | 
 | ||||||
| def to_dict(cur, first=False): | def to_dict(cur, first=False): | ||||||
|     ret = [] |     ret = [] | ||||||
|  | @ -83,7 +99,7 @@ class User(BaseModel): | ||||||
|         if not data: |         if not data: | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|         if bcrypt.check_password_hash(data['password'], password): |         if bcrypt.checkpw(password, data['password']): | ||||||
|             login_user(CurrentUser(data['id'])) |             login_user(CurrentUser(data['id'])) | ||||||
|             session['user'] = data |             session['user'] = data | ||||||
|             return True |             return True | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import time | import time | ||||||
| from functools import update_wrapper | from functools import update_wrapper | ||||||
| from flask import request, g | from flask import request, g | ||||||
| from realms import app, cache | from realms import app | ||||||
|  | from models import cache | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RateLimit(object): | class RateLimit(object): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue