Merge branch 'fix/multsite'

This commit is contained in:
Martin Zimmermann 2013-12-08 19:20:46 +01:00
commit 990688f6e0
9 changed files with 90 additions and 85 deletions

View File

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

View File

@ -38,6 +38,7 @@ session key and hostname. Here are the default values for this section:
[general] [general]
dbpath = /tmp/isso.db dbpath = /tmp/isso.db
name =
host = http://localhost:8080/ host = http://localhost:8080/
max-age = 15m max-age = 15m
session-key = ... ; python: binascii.b2a_hex(os.urandom(24)) 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 file location to the SQLite3 database, highly recommended to change this
location to a non-temporary location! location to a non-temporary location!
name
required to dispatch :ref:`multiple websites <configure-multiple-sites>`,
not used otherwise.
host host
URL to your website. When you start Isso, it will probe your website with 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 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 ( 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 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 .. code-block:: sh
$ cat /etc/isso.d/foo.example.cfg $ cat /etc/isso.d/foo.example.cfg
[general] [general]
name = foo
host = http://foo.example/ host = http://foo.example/
dbpath = /var/lib/isso/foo.example.db dbpath = /var/lib/isso/foo.example.db
$ cat /etc/isso.d/other.foo.cfg $ cat /etc/isso.d/other.bar.cfg
[general] [general]
host = http://other.foo/ name = bar
dbpath = /var/lib/isso/other.foo.db 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 .. 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 $ gunicorn isso.dispatch -b localhost:8080
In your webserver configuration, proxy Isso as usual: In your webserver configuration, proxy Isso as usual:
@ -73,18 +76,18 @@ In your webserver configuration, proxy Isso as usual:
location / { location / {
proxy_pass http://localhost:8080; 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 http://comments.example/
... /foo
$ curl -vH "Origin: http://other.foo" http://comments.example/ /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 # location to a non-temporary location
dbpath = /tmp/comments.db 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 # 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 # 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. # 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 = [ default = [
"[general]", "[general]",
"name = ",
"dbpath = /tmp/isso.db", "session-key = %r" % binascii.b2a_hex(os.urandom(24)), "dbpath = /tmp/isso.db", "session-key = %r" % binascii.b2a_hex(os.urandom(24)),
"host = http://localhost:8080/", "max-age = 15m", "host = http://localhost:8080/", "max-age = 15m",
"notify = ", "notify = ",

61
isso/dispatch.py Normal file
View File

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

View File

@ -87,10 +87,6 @@ define(["q"], function(Q) {
xhr.withCredentials = true; xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Content-Type", "application/json");
if (method === "GET") {
xhr.setRequestHeader("X-Origin", window.location.origin);
}
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
onload(); onload();

View File

@ -69,7 +69,7 @@ class CORSMiddleware(object):
def add_cors_headers(status, headers, exc_info=None): def add_cors_headers(status, headers, exc_info=None):
headers = Headers(headers) headers = Headers(headers)
headers.add("Access-Control-Allow-Origin", self.origin(environ)) 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-Credentials", "true")
headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
headers.add("Access-Control-Expose-Headers", "X-Set-Cookie") 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"}) rv = client.get("/", headers={"ORIGIN": "https://example.tld"})
assert rv.headers["Access-Control-Allow-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-Credentials"] == "true"
assert rv.headers["Access-Control-Allow-Methods"] == "GET, POST, PUT, DELETE" assert rv.headers["Access-Control-Allow-Methods"] == "GET, POST, PUT, DELETE"
assert rv.headers["Access-Control-Expose-Headers"] == "X-Set-Cookie" assert rv.headers["Access-Control-Expose-Headers"] == "X-Set-Cookie"