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.
This commit is contained in:
Martin Zimmermann 2013-12-08 19:03:10 +01:00
parent ac74418179
commit b15f17738e
9 changed files with 48 additions and 43 deletions

View File

@ -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 <configure-multiple-sites>`,
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

View File

@ -42,25 +42,28 @@ websites on different domains.
The following example uses `gunicorn <http://gunicorn.org/>`_ 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.

View File

@ -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.

View File

@ -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 = ",

View File

@ -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)
super(Dispatcher, self).__init__(self.default, mounts=self.isso)
def __call__(self, environ, start_response):
if Request(environ).url.endswith((".js", ".css")):
return self.isso.values()[0](environ, start_response)
# clear X-Script-Name as the PATH_INFO is already adjusted
environ.pop('HTTP_X_SCRIPT_NAME', None)
if "HTTP_X_ORIGIN" in environ and "HTTP_ORIGIN" not in environ:
environ["HTTP_ORIGIN"] = environ["HTTP_X_ORIGIN"]
return super(Dispatcher, self).__call__(environ, start_response)
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)
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))

View File

@ -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();

0
isso/run.py Normal file
View File

View File

@ -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")

View File

@ -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"