From 3218e16532644a020101b8d24773d17ce98887d9 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Thu, 12 Sep 2013 17:26:10 +0200 Subject: [PATCH] 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. --- isso/__init__.py | 30 ++++++++++++++++++++++-------- isso/js/app/api.js | 23 ++++++++++++++++------- isso/wsgi.py | 16 ++++++++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 isso/wsgi.py diff --git a/isso/__init__.py b/isso/__init__.py index b6242e2..be0fb63 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -39,14 +39,15 @@ from itsdangerous import URLSafeTimedSerializer from werkzeug.routing import Map, Rule 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.serving import run_simple +from werkzeug.contrib.fixers import ProxyFix 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 url_map = Map([ @@ -64,10 +65,10 @@ class Isso(object): 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.BASE_URL = utils.normalize(base_url) + self.ORIGIN = utils.normalize(origin) self.PASSPHRASE = passphrase self.MAX_AGE = max_age @@ -101,6 +102,11 @@ class Isso(object): return handler(self, request.environ, request, **values) except NotFound as e: 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: return e except InternalServerError as e: @@ -111,6 +117,14 @@ class Isso(object): return response(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) @@ -131,7 +145,7 @@ def main(): defaultcfg = [ "[general]", "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", "[server]", "host = localhost", "port = 8080" @@ -145,7 +159,7 @@ def main(): isso = Isso( dbpath=conf.get('general', 'dbpath'), secret=conf.get('general', 'secret'), - base_url=conf.get('general', 'base_url'), + origin=conf.get('general', 'host'), max_age=conf.getint('general', 'max_age'), passphrase=conf.get('general', 'passphrase') ) @@ -154,10 +168,10 @@ def main(): migrate.disqus(isso.db, args.dump) sys.exit(0) - app = SharedDataMiddleware(isso.wsgi_app, { + app = wsgi.SubURI(SharedDataMiddleware(isso.wsgi_app, { '/static': join(dirname(__file__), 'static/'), '/js': join(dirname(__file__), 'js/') - }) + })) run_simple(conf.get('server', 'host'), conf.getint('server', 'port'), app, processes=2) diff --git a/isso/js/app/api.js b/isso/js/app/api.js index a751511..514780f 100644 --- a/isso/js/app/api.js +++ b/isso/js/app/api.js @@ -22,25 +22,34 @@ define(["lib/q"], function(Q) { endpoint = js[i].src.substring(0, js[i].src.length - 12); break; } + } + if (endpoint == null) { throw "no Isso API location found"; } var curl = function(method, url, data) { - var request = new XMLHttpRequest(); + var xhr = new XMLHttpRequest(); var response = Q.defer(); + if (! ("withCredentials" in xhr)) { + respone.reject("I won't support IE ≤ 10.") + return response.promise; + } + + xhr.withCredentials = true; + function onload() { - response.resolve({status: request.status, body: request.responseText}); + response.resolve({status: xhr.status, body: xhr.responseText}); } try { - request.open(method, url, true); - request.overrideMimeType("application/javascript"); + xhr.open(method, url, true); + xhr.overrideMimeType("application/javascript"); - request.onreadystatechange = function () { - if (request.readyState === 4) { + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { onload(); } }; @@ -48,7 +57,7 @@ define(["lib/q"], function(Q) { response.reject(exception.message); } - request.send(data); + xhr.send(data); return response.promise; }; diff --git a/isso/wsgi.py b/isso/wsgi.py new file mode 100644 index 0000000..b31f136 --- /dev/null +++ b/isso/wsgi.py @@ -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)