From b15f17738e6d3b159ba30cf8b3cfd8afa5a45f53 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 8 Dec 2013 19:03:10 +0100 Subject: [PATCH] isso.dispatch now dispatches multiple sites based on relative URLs The previous approach using a custom X-Custom header did work for the client-side, but not for activation and deletion links. Now, you need to add a `name = foo` option to the general section. `isso.dispatch` then binds this configuration to /foo and can distinguish all API calls without a special HTTP header. --- docs/docs/configuration/server.rst | 5 ++++ docs/docs/configuration/setup.rst | 33 +++++++++++++----------- docs/example.conf | 3 +++ isso/core.py | 1 + isso/dispatch.py | 41 ++++++++++++++---------------- isso/js/app/api.js | 4 --- isso/run.py | 0 isso/wsgi.py | 2 +- specs/test_cors.py | 2 +- 9 files changed, 48 insertions(+), 43 deletions(-) create mode 100644 isso/run.py diff --git a/docs/docs/configuration/server.rst b/docs/docs/configuration/server.rst index 0e964f8..7615b3c 100644 --- a/docs/docs/configuration/server.rst +++ b/docs/docs/configuration/server.rst @@ -38,6 +38,7 @@ session key and hostname. Here are the default values for this section: [general] dbpath = /tmp/isso.db + name = host = http://localhost:8080/ max-age = 15m session-key = ... ; python: binascii.b2a_hex(os.urandom(24)) @@ -47,6 +48,10 @@ dbpath file location to the SQLite3 database, highly recommended to change this location to a non-temporary location! +name + required to dispatch :ref:`multiple websites `, + not used otherwise. + host URL to your website. When you start Isso, it will probe your website with a simple ``GET /`` request to see if it can reach the webserver. If this diff --git a/docs/docs/configuration/setup.rst b/docs/docs/configuration/setup.rst index adc1584..b788551 100644 --- a/docs/docs/configuration/setup.rst +++ b/docs/docs/configuration/setup.rst @@ -42,25 +42,28 @@ websites on different domains. The following example uses `gunicorn `_ as WSGI server ( you can use uWSGI as well). Let's say you maintain two websites, like -foo.example and other.foo: +foo.example and other.bar: .. code-block:: sh $ cat /etc/isso.d/foo.example.cfg [general] + name = foo host = http://foo.example/ dbpath = /var/lib/isso/foo.example.db - $ cat /etc/isso.d/other.foo.cfg + $ cat /etc/isso.d/other.bar.cfg [general] - host = http://other.foo/ - dbpath = /var/lib/isso/other.foo.db + name = bar + host = http://other.bar/ + dbpath = /var/lib/isso/other.bar.db -Then you run Isso using gunicorn: +Then you run Isso with gunicorn (separate multiple configuration files by +semicolon): .. code-block:: sh - $ export ISSO_SETTINGS="/etc/isso.d/foo.example.cfg;/etc/isso.d/other.foo.cfg" + $ export ISSO_SETTINGS="/etc/isso.d/foo.example.cfg;/etc/isso.d/other.bar.cfg" $ gunicorn isso.dispatch -b localhost:8080 In your webserver configuration, proxy Isso as usual: @@ -73,18 +76,18 @@ In your webserver configuration, proxy Isso as usual: location / { proxy_pass http://localhost:8080; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; } } -To verify the setup, run: +When you now visit http://comments.example/, you will see your different Isso +configuration separated by /`name`. -.. code-block:: sh +.. code-block:: text - $ curl -vH "Origin: http://foo.example" http://comments.example/ - ... - $ curl -vH "Origin: http://other.foo" http://comments.example/ - ... + $ curl http://comments.example/ + /foo + /bar -In case of a 418 (I'm a teapot), the setup is *not* correctly configured. +Just embed the JavaScript including the new relative path, e.g. +*http://comments.example/foo/js/embed.min.js*. Make sure, you don't mix the +URLs on both sites as it will most likely cause CORS-related errors. diff --git a/docs/example.conf b/docs/example.conf index 20b7961..70d5a07 100644 --- a/docs/example.conf +++ b/docs/example.conf @@ -7,6 +7,9 @@ # location to a non-temporary location dbpath = /tmp/comments.db +# required to dispatch multiple websites, not used otherwise. +name = + # URL to your website. When you start Isso, it will probe your website with a # simple GET / request to see if it can reach the webserver. If this fails, Isso # may not be able check if a web page exists, thus fails to accept new comments. diff --git a/isso/core.py b/isso/core.py index e030685..066194d 100644 --- a/isso/core.py +++ b/isso/core.py @@ -105,6 +105,7 @@ class Config: default = [ "[general]", + "name = ", "dbpath = /tmp/isso.db", "session-key = %r" % binascii.b2a_hex(os.urandom(24)), "host = http://localhost:8080/", "max-age = 15m", "notify = ", diff --git a/isso/dispatch.py b/isso/dispatch.py index 04d64f7..fd13b98 100644 --- a/isso/dispatch.py +++ b/isso/dispatch.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- import os import logging @@ -7,8 +8,8 @@ try: except ImportError: from urllib.parse import urlparse -from werkzeug.wrappers import Request -from werkzeug.exceptions import ImATeapot +from werkzeug.wsgi import DispatcherMiddleware +from werkzeug.wrappers import Response from isso import make_app, wsgi from isso.core import Config @@ -16,40 +17,36 @@ from isso.core import Config logger = logging.getLogger("isso") -class Dispatcher(object): +class Dispatcher(DispatcherMiddleware): """ A dispatcher to support different websites. Dispatches based on - HTTP-Host. If HTTP-Host is not provided, display an error message. + a relative URI, e.g. /foo.example and /other.bar. """ def __init__(self, *confs): self.isso = {} - for conf in map(Config.load, confs): + for i, conf in enumerate(map(Config.load, confs)): - app = make_app(conf) + if not conf.get("general", "name"): + logger.warn("unable to dispatch %r, no 'name' set", confs[i]) + continue - for origin in conf.getiter("general", "host"): - self.isso[origin.rstrip("/")] = app + self.isso["/" + conf.get("general", "name")] = make_app(conf) - def __call__(self, environ, start_response): + super(Dispatcher, self).__init__(self.default, mounts=self.isso) - if Request(environ).url.endswith((".js", ".css")): - return self.isso.values()[0](environ, start_response) + def __call__(self, environ, start_response): - if "HTTP_X_ORIGIN" in environ and "HTTP_ORIGIN" not in environ: - environ["HTTP_ORIGIN"] = environ["HTTP_X_ORIGIN"] + # clear X-Script-Name as the PATH_INFO is already adjusted + environ.pop('HTTP_X_SCRIPT_NAME', None) - origin = environ.get("HTTP_ORIGIN", wsgi.host(environ)) + return super(Dispatcher, self).__call__(environ, start_response) - try: - # logger.info("dispatch %s", origin) - return self.isso[origin](environ, start_response) - except KeyError: - # logger.info("unable to dispatch %s", origin) - resp = ImATeapot("unable to dispatch %s" % origin) - return resp(environ, start_response) + def default(self, environ, start_response): + resp = Response("\n".join(self.isso.keys()), 404, content_type="text/plain") + return resp(environ, start_response) if "ISSO_SETTINGS" not in os.environ: @@ -61,4 +58,4 @@ else: logger.fatal("%s: no such file", path) break else: - application = Dispatcher(*confs) + application = wsgi.SubURI(Dispatcher(*confs)) diff --git a/isso/js/app/api.js b/isso/js/app/api.js index a2736fb..ece73b5 100644 --- a/isso/js/app/api.js +++ b/isso/js/app/api.js @@ -87,10 +87,6 @@ define(["q"], function(Q) { xhr.withCredentials = true; xhr.setRequestHeader("Content-Type", "application/json"); - if (method === "GET") { - xhr.setRequestHeader("X-Origin", window.location.origin); - } - xhr.onreadystatechange = function () { if (xhr.readyState === 4) { onload(); diff --git a/isso/run.py b/isso/run.py new file mode 100644 index 0000000..e69de29 diff --git a/isso/wsgi.py b/isso/wsgi.py index 35ddd84..4d80bf6 100644 --- a/isso/wsgi.py +++ b/isso/wsgi.py @@ -69,7 +69,7 @@ class CORSMiddleware(object): def add_cors_headers(status, headers, exc_info=None): headers = Headers(headers) headers.add("Access-Control-Allow-Origin", self.origin(environ)) - headers.add("Access-Control-Allow-Headers", "Origin, Content-Type, X-Origin") + headers.add("Access-Control-Allow-Headers", "Origin, Content-Type") headers.add("Access-Control-Allow-Credentials", "true") headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") headers.add("Access-Control-Expose-Headers", "X-Set-Cookie") diff --git a/specs/test_cors.py b/specs/test_cors.py index 6b3338c..fe11fc1 100644 --- a/specs/test_cors.py +++ b/specs/test_cors.py @@ -26,7 +26,7 @@ def test_simple_CORS(): rv = client.get("/", headers={"ORIGIN": "https://example.tld"}) assert rv.headers["Access-Control-Allow-Origin"] == "https://example.tld" - assert rv.headers["Access-Control-Allow-Headers"] == "Origin, Content-Type, X-Origin" + assert rv.headers["Access-Control-Allow-Headers"] == "Origin, Content-Type" assert rv.headers["Access-Control-Allow-Credentials"] == "true" assert rv.headers["Access-Control-Allow-Methods"] == "GET, POST, PUT, DELETE" assert rv.headers["Access-Control-Expose-Headers"] == "X-Set-Cookie"