Merge branch 'fix/multsite'
This commit is contained in:
commit
990688f6e0
64
dispatch.py
64
dispatch.py
@ -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)
|
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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
61
isso/dispatch.py
Normal 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))
|
@ -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();
|
||||||
|
@ -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")
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user