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
monkey.patch_all()
import logging
from realms import app, config
from realms import create_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__':
app.logger.setLevel(logging.INFO)
app = SubdomainDispatcher(config.domain, make_app)
pywsgi.WSGIServer(('', config.port), app).serve_forever()

View file

@ -2,19 +2,17 @@ import logging
import os
import time
import redis
import rethinkdb as rdb
from flask import Flask, request, render_template, url_for, redirect, flash, session
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager, login_user, logout_user
from flask.ext.assets import Environment
from recaptcha.client import captcha
from werkzeug.routing import BaseConverter
from session import RedisSessionInterface
import config
from wiki import Wiki
from util import to_canonical, remove_ext, mkdir_safe, gravatar_url
from models import Site, User, CurrentUser
class RegexConverter(BaseConverter):
@ -22,48 +20,6 @@ class RegexConverter(BaseConverter):
super(RegexConverter, self).__init__(url_map)
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():
return request.args.get('next') or request.referrer or url_for('index')
@ -73,39 +29,69 @@ def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
app.config['RECAPTCHA_PRIVATE_KEY'],
config.flask['RECAPTCHA_PRIVATE_KEY'],
request.remote_addr)
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))
@app.errorhandler(404)
def page_not_found(e):
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def page_error(e):
@app.errorhandler(500)
def page_error(e):
logging.exception(e)
return render_template('errors/500.html'), 500
@app.route("/")
def root():
@app.route("/")
def root():
return render('home')
#return redirect('/home')
@app.route("/account/")
def account():
@app.route("/account/")
def account():
return render_template('account/index.html')
@app.route("/_new/", methods=['GET', 'POST'])
def new_wiki():
@app.route("/_new/", methods=['GET', 'POST'])
def new_wiki():
if request.method == 'POST':
# TODO validate wiki name
wiki_name = request.form['name']
@ -120,14 +106,14 @@ def new_wiki():
return render_template('_new/index.html')
@app.route("/logout/")
def logout():
@app.route("/logout/")
def logout():
logout_user()
del session['user']
return redirect(url_for('root'))
@app.route("/commit/<sha>/<name>")
def commit_sha(name, sha):
@app.route("/commit/<sha>/<name>")
def commit_sha(name, sha):
cname = to_canonical(name)
data = w.get_page(cname, sha=sha)
@ -137,14 +123,14 @@ def commit_sha(name, sha):
return redirect('/create/'+cname)
@app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
@app.route("/compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
diff = w.compare(name, fsha, lsha)
return render_template('page/compare.html', name=name, diff=diff)
@app.route("/register", methods=['GET', 'POST'])
def register():
@app.route("/register", methods=['GET', 'POST'])
def register():
if request.method == 'POST':
user = User()
if user.get_by_email(request.form['email']):
@ -166,8 +152,8 @@ def register():
return render_template('account/register.html')
@app.route("/login", methods=['GET', 'POST'])
def login():
@app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
if User.auth(request.form['email'], request.form['password']):
return redirect("/")
@ -178,14 +164,14 @@ def login():
return render_template('account/login.html')
@app.route("/history/<name>")
def history(name):
@app.route("/history/<name>")
def history(name):
history = w.get_history(name)
return render_template('page/history.html', name=name, history=history)
@app.route("/edit/<name>", methods=['GET', 'POST'])
def edit(name):
@app.route("/edit/<name>", methods=['GET', 'POST'])
def edit(name):
data = w.get_page(name)
cname = to_canonical(name)
if request.method == 'POST':
@ -203,14 +189,14 @@ def edit(name):
return redirect('/create/'+cname)
@app.route("/delete/<name>", methods=['POST'])
def delete(name):
@app.route("/delete/<name>", methods=['POST'])
def delete(name):
pass
@app.route("/create/", methods=['GET', 'POST'])
@app.route("/create/<name>", methods=['GET', 'POST'])
def create(name=None):
@app.route("/create/", methods=['GET', 'POST'])
@app.route("/create/<name>", methods=['GET', 'POST'])
def create(name=None):
cname = ""
if name:
cname = to_canonical(name)
@ -224,8 +210,8 @@ def create(name=None):
return render_template('page/edit.html', name=cname, content="")
@app.route("/<name>")
def render(name):
@app.route("/<name>")
def render(name):
cname = to_canonical(name)
if cname != name:
return redirect('/' + cname)
@ -236,4 +222,4 @@ def render(name):
else:
return redirect('/create/'+cname)
import ratelimit
return app

View file

@ -1,9 +1,25 @@
import rethinkdb as rdb
import bcrypt
import redis
from flask import session
from flask.ext.login import login_user
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):
ret = []
@ -83,7 +99,7 @@ class User(BaseModel):
if not data:
return False
if bcrypt.check_password_hash(data['password'], password):
if bcrypt.checkpw(password, data['password']):
login_user(CurrentUser(data['id']))
session['user'] = data
return True

View file

@ -1,7 +1,8 @@
import time
from functools import update_wrapper
from flask import request, g
from realms import app, cache
from realms import app
from models import cache
class RateLimit(object):