subdomain dispatcher

This commit is contained in:
Matthew Scragg 2013-10-03 21:57:19 -05:00
parent 27ced9d90e
commit 2906b79dfc
4 changed files with 215 additions and 185 deletions

33
app.py
View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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):