Flask app that routes to multiple top level and su

2019-07-19 20:44发布

I have an app that requires multiple domains pointing to the same app with different data being displayed, but I also have the same "admin" subdomain across all domains which also displays different data depending on the domain.

An example would be:

pinetree.com - displays information about pine trees
oaktree.com - displays information about oak trees

admin.pinetree.com - displays admin for managing pine trees
admin.oaktree.com - displays admin for managing oak trees

So far, I've found that you need to write the SERVER_NAME (domain name) in the Flask config in order to use subdomains with Flask, but since I have many different types of trees with unique domains, and new trees are added all the time, I don't see how I could use that functionality.

Also, I have seen that GAE flexible doesn't have multitenancy, which is what I had first thought would be the way to manage multiple domains on GAE.

2条回答
劫难
2楼-- · 2019-07-19 21:22

Here's a little library I made a few years ago which might be helpful: https://github.com/wiota/landlord (this pattern is often called "multi-tenancy" or a "multi-tenant app", hence the name).

This is useful when you want to respond with essentially the same application with different content depending on the hostname, which seems to be what you're looking for.

In a nutshell, you wrap your existing app like this in your app.py:

from flask import Flask
from landlord import Landlord
from your_app import create_app

if __name__ == '__main__' :
    app = Flask(__name__)
    app.wsgi_app = Landlord(create_app)
    app.run()

Then in your sub-application's __init__.py, the create_app function gets a hostname parameter, which allows you do do per-host logic:

from flask import Flask

def create_app(hostname):
    app = Flask(__name__)
    app.config['HOST'] = hostname

    @app.route('/')
    def root():
        return "Currently running on host: %s" % (app.config['HOST'])

    return app
查看更多
劳资没心,怎么记你
3楼-- · 2019-07-19 21:28

Subdomain matching, explained in another answer, should be used if you have one base domain with several subdomains. It's more straightforward since Flask can infer more about the URLs it's matching.

However, if you have multiple base domains, you should use host matching instead. You must set host_matching=True on the app object, as well as setting static_host so the static route knows which host to to serve from. Unlike subdomains, you do not set SERVER_NAME. Then pass the host option to routes. This matches against the full domain, and so it requires writing out the full domain each time, rather than just the subdomain.

Unfortunately, matching the full host means matching the port as well. Under the dev server, the port will be 5000 by default, but in production the port may be 80, 443, or something else. You can write a small helper to set the port to 5000 when running in development mode (or whatever configuration logic you need for your deployment).

from flask.helpers import get_env

def p(host):
    if get_env() == "development":
        return host + ":5000"

    return host

# p("example.com") -> "example.com:5000"

This example shows routing to any host of the form {tree}tree.com and admin.{tree}tree.com, with pinetree.com as the static host.

from flask import Flask
app = Flask(__name__, host_matching=True, static_host=p("pinetree.com"))

@app.route("/", host=p("<tree>tree.com"))
def index(tree):
    return f"{tree} tree"

Blueprint does not accept a host option yet, so you'll need to specify the host for each route. You can simplify this a bit using partial.

from functools import partial
from flask import Blueprint

admin = Blueprint("admin", __name__)
admin_route = partial(admin.route, host=p("admin.<tree>tree.com"))

@admin_route("/")
def index(tree):
    return f"admin for {tree} tree"

app.register_blueprint(admin)

Note that the host can take URL parameters just like the path in the route. It will be passed to views just like path parameters. This allows for dynamic hosts and subdomains. You can use @app.url_defaults and @app.url_value_preprocessor to extract this into g instead of writing it as an argument for each view.

from flask import g

@app.url_value_preprocessor
def extract_tree(endpoint, values):
    g.tree = values.pop("tree")

@app.url_defaults
def inject_tree(endpoint, values):
    values.setdefault("tree", g.tree)

@app.route("/")
def index()
    return f"{g.tree} tree"

During development, add the hosts your hosts file (/etc/hosts on Unix so they route to localhost.

127.0.0.1 localhost pinetree.com admin.pinetree.com oaktree.com admin.oaktree.com

And run with:

export FLASK_ENV=development
flask run
查看更多
登录 后发表回答