diff --git a/dispatch.py b/dispatch.py deleted file mode 100644 index 04d64f7..0000000 --- a/dispatch.py +++ /dev/null @@ -1,64 +0,0 @@ - -import os -import logging - -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - -from werkzeug.wrappers import Request -from werkzeug.exceptions import ImATeapot - -from isso import make_app, wsgi -from isso.core import Config - -logger = logging.getLogger("isso") - - -class Dispatcher(object): - """ - A dispatcher to support different websites. Dispatches based on - HTTP-Host. If HTTP-Host is not provided, display an error message. - """ - - def __init__(self, *confs): - - self.isso = {} - - for conf in map(Config.load, confs): - - app = make_app(conf) - - for origin in conf.getiter("general", "host"): - self.isso[origin.rstrip("/")] = app - - def __call__(self, environ, start_response): - - if Request(environ).url.endswith((".js", ".css")): - return self.isso.values()[0](environ, start_response) - - if "HTTP_X_ORIGIN" in environ and "HTTP_ORIGIN" not in environ: - environ["HTTP_ORIGIN"] = environ["HTTP_X_ORIGIN"] - - origin = environ.get("HTTP_ORIGIN", wsgi.host(environ)) - - 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) - - -if "ISSO_SETTINGS" not in os.environ: - logger.fatal('no such environment variable: ISSO_SETTINGS') -else: - confs = os.environ["ISSO_SETTINGS"].split(";") - for path in confs: - if not os.path.isfile(path): - logger.fatal("%s: no such file", path) - break - else: - application = Dispatcher(*confs) 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 new file mode 100644 index 0000000..fd13b98 --- /dev/null +++ b/isso/dispatch.py @@ -0,0 +1,61 @@ +# -*- encoding: utf-8 -*- + +import os +import logging + +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + +from werkzeug.wsgi import DispatcherMiddleware +from werkzeug.wrappers import Response + +from isso import make_app, wsgi +from isso.core import Config + +logger = logging.getLogger("isso") + + +class Dispatcher(DispatcherMiddleware): + """ + A dispatcher to support different websites. Dispatches based on + a relative URI, e.g. /foo.example and /other.bar. + """ + + def __init__(self, *confs): + + self.isso = {} + + for i, conf in enumerate(map(Config.load, confs)): + + if not conf.get("general", "name"): + logger.warn("unable to dispatch %r, no 'name' set", confs[i]) + continue + + self.isso["/" + conf.get("general", "name")] = make_app(conf) + + super(Dispatcher, self).__init__(self.default, mounts=self.isso) + + def __call__(self, environ, start_response): + + # clear X-Script-Name as the PATH_INFO is already adjusted + environ.pop('HTTP_X_SCRIPT_NAME', None) + + return super(Dispatcher, self).__call__(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: + logger.fatal('no such environment variable: ISSO_SETTINGS') +else: + confs = os.environ["ISSO_SETTINGS"].split(";") + for path in confs: + if not os.path.isfile(path): + logger.fatal("%s: no such file", path) + break + else: + 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/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"