add CORS support

Isso can now run on a separate domain such as comments.example.org
and still serve for blog.example.org using CORS.
This commit is contained in:
Martin Zimmermann 2013-09-12 17:26:10 +02:00
parent 45d4e18aef
commit 3218e16532
3 changed files with 54 additions and 15 deletions

View File

@ -39,14 +39,15 @@ from itsdangerous import URLSafeTimedSerializer
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule
from werkzeug.wrappers import Response, Request from werkzeug.wrappers import Response, Request
from werkzeug.exceptions import HTTPException, NotFound, InternalServerError from werkzeug.exceptions import HTTPException, NotFound, InternalServerError, MethodNotAllowed
from werkzeug.wsgi import SharedDataMiddleware from werkzeug.wsgi import SharedDataMiddleware
from werkzeug.serving import run_simple from werkzeug.serving import run_simple
from werkzeug.contrib.fixers import ProxyFix
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from isso import db, utils, migrate, views from isso import db, utils, migrate, views, wsgi
from isso.views import comment, admin from isso.views import comment, admin
url_map = Map([ url_map = Map([
@ -64,10 +65,10 @@ class Isso(object):
PRODUCTION = False PRODUCTION = False
def __init__(self, dbpath, secret, base_url, max_age, passphrase): def __init__(self, dbpath, secret, origin, max_age, passphrase):
self.DBPATH = dbpath self.DBPATH = dbpath
self.BASE_URL = utils.normalize(base_url) self.ORIGIN = utils.normalize(origin)
self.PASSPHRASE = passphrase self.PASSPHRASE = passphrase
self.MAX_AGE = max_age self.MAX_AGE = max_age
@ -101,6 +102,11 @@ class Isso(object):
return handler(self, request.environ, request, **values) return handler(self, request.environ, request, **values)
except NotFound as e: except NotFound as e:
return Response('Not Found', 404) return Response('Not Found', 404)
except MethodNotAllowed:
return Response("", 200, headers={
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Origin, Content-Type"
})
except HTTPException as e: except HTTPException as e:
return e return e
except InternalServerError as e: except InternalServerError as e:
@ -111,6 +117,14 @@ class Isso(object):
return response(environ, start_response) return response(environ, start_response)
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
return self.wsgi_app(environ, start_response) return self.wsgi_app(environ, start_response)
@ -131,7 +145,7 @@ def main():
defaultcfg = [ defaultcfg = [
"[general]", "[general]",
"dbpath = /tmp/isso.db", "secret = %r" % os.urandom(24), "dbpath = /tmp/isso.db", "secret = %r" % os.urandom(24),
"base_url = http://localhost:8080/", "passphrase = p@$$w0rd", "host = http://localhost:8080/", "passphrase = p@$$w0rd",
"max_age = 450", "max_age = 450",
"[server]", "[server]",
"host = localhost", "port = 8080" "host = localhost", "port = 8080"
@ -145,7 +159,7 @@ def main():
isso = Isso( isso = Isso(
dbpath=conf.get('general', 'dbpath'), dbpath=conf.get('general', 'dbpath'),
secret=conf.get('general', 'secret'), secret=conf.get('general', 'secret'),
base_url=conf.get('general', 'base_url'), origin=conf.get('general', 'host'),
max_age=conf.getint('general', 'max_age'), max_age=conf.getint('general', 'max_age'),
passphrase=conf.get('general', 'passphrase') passphrase=conf.get('general', 'passphrase')
) )
@ -154,10 +168,10 @@ def main():
migrate.disqus(isso.db, args.dump) migrate.disqus(isso.db, args.dump)
sys.exit(0) sys.exit(0)
app = SharedDataMiddleware(isso.wsgi_app, { app = wsgi.SubURI(SharedDataMiddleware(isso.wsgi_app, {
'/static': join(dirname(__file__), 'static/'), '/static': join(dirname(__file__), 'static/'),
'/js': join(dirname(__file__), 'js/') '/js': join(dirname(__file__), 'js/')
}) }))
run_simple(conf.get('server', 'host'), conf.getint('server', 'port'), run_simple(conf.get('server', 'host'), conf.getint('server', 'port'),
app, processes=2) app, processes=2)

View File

@ -22,25 +22,34 @@ define(["lib/q"], function(Q) {
endpoint = js[i].src.substring(0, js[i].src.length - 12); endpoint = js[i].src.substring(0, js[i].src.length - 12);
break; break;
} }
}
if (endpoint == null) {
throw "no Isso API location found"; throw "no Isso API location found";
} }
var curl = function(method, url, data) { var curl = function(method, url, data) {
var request = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var response = Q.defer(); var response = Q.defer();
if (! ("withCredentials" in xhr)) {
respone.reject("I won't support IE ≤ 10.")
return response.promise;
}
xhr.withCredentials = true;
function onload() { function onload() {
response.resolve({status: request.status, body: request.responseText}); response.resolve({status: xhr.status, body: xhr.responseText});
} }
try { try {
request.open(method, url, true); xhr.open(method, url, true);
request.overrideMimeType("application/javascript"); xhr.overrideMimeType("application/javascript");
request.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (request.readyState === 4) { if (xhr.readyState === 4) {
onload(); onload();
} }
}; };
@ -48,7 +57,7 @@ define(["lib/q"], function(Q) {
response.reject(exception.message); response.reject(exception.message);
} }
request.send(data); xhr.send(data);
return response.promise; return response.promise;
}; };

16
isso/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
class SubURI(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
return self.app(environ, start_response)