From 6f66960df857695958d605370a05a31c12fd9550 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 17 Nov 2013 00:35:32 +0100 Subject: [PATCH] add an isso.dispatch module to support multiple sites, #34 --- dispatch.py | 63 ++++++++++++++++++++++++++++++++ docs/CONFIGURATION.rst | 83 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 dispatch.py diff --git a/dispatch.py b/dispatch.py new file mode 100644 index 0000000..08758c6 --- /dev/null +++ b/dispatch.py @@ -0,0 +1,63 @@ + +import os +import logging + +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + +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 "HTTP_ORIGIN" in environ: + origin = environ["HTTP_ORIGIN"] + elif "HTTP_REFERER" in environ: + rv = urlparse(environ["HTTP_REFERER"]) + origin = rv.scheme + "://" + rv.hostname + (":" + str(rv.port) if rv.port else "") + else: + 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/CONFIGURATION.rst b/docs/CONFIGURATION.rst index 67aec97..2ee722d 100644 --- a/docs/CONFIGURATION.rst +++ b/docs/CONFIGURATION.rst @@ -208,6 +208,89 @@ reply-to-self Do not forget to configure the client. +Multiple Sites +-------------- + +Isso is designed to serve comments for a single website and therefore stores +comments for a relative URL to support HTTP, HTTPS and even domain transfers +without manual intervention. But you can chain Isso to support multiple +websites on different domains. + +The following example uses [gunicorn](http://gunicorn.org/) as WSGI server (you +can use uWSGI as well). It is *not* possible to run the isso executable for +multiple sites. + +Let's say you maintain two websites, like foo.example and other.foo: + +.. code-block:: bash + + $ cat /etc/isso.d/foo.example.cfg + [general] + host = http://foo.example/ + dbpath = /var/lib/isso/foo.example.db + $ cat /etc/isso.d/other.foo.cfg + [general] + host = http://other.foo/ + dbpath = /var/lib/isso/other.foo.db + $ export ISSO_SETTINGS="/etc/isso.d/foo.example.cfg;/etc/isso.d/other.foo.cfg" + $ gunicorn isso.dispatch -b localhost:8080 + +Now, there are two options to configure the webserver: + + 1. using a single host to serve comments for both websites + 2. different hosts for both websites + +In the former case, Isso dispatches based on the HTTP Referer and (if provided) +HTTP Origin. If you expect users to supress their referer completely, you +should use the second option. + +1. Using a single host to serve comments. + + .. code-block:: nginx + + server { + listen [::]:80; + server_name comments.example; + + 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: + + .. code-block:: bash + + $ curl -vH "Origin: http://foo.example" http://comments.example/ + ... + $ curl -vH "Origin: http://other.foo" http://comments.example/ + ... + + In case of a 418 (I'm a teapot), the setup is *not* correctly configured. + +2. Using different hosts for both websites (no need for a dedicated domain, + you can also proxy Isso on a sub-uri like /isso). + + .. code-block:: nginx + + server { + listen [::]:80; + server_name comments.foo.example comments.other.foo; + + location / { + proxy_pass http://localhost:8080; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } + } + + No need to verify this setup, here the webserver automatically sets the + proper host. + + Appendum ---------