Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serve multiple domains which share the application back-end in Flask?

I have two web domains, call them alpha.com and beta.com. I want to run two different public facing websites on these two different domains, but from a shared server and within the same flask app and application context. These websites will both share the backend app and database,

My current project structure is below:

app/
  -static/
    --alphacom_static/
    --betacom_static/
    --shared_backend_static/
  -blueprints/
    --alphacom_frontend_bluprint/
    --betacom_frontend_blueprint/
    --shared_backend_blueprint/

I'm serving beta.com running on localhost with flask/gevent through a reverse proxy on the server. I plan to just add a blueprint for alpha.com.

The route of the beta.com landing page is @blueprint.route(r'/', methods=['GET', 'POST']). When a user logs in at beta.com/login then they get directed to beta.com/app.

What is the approach using blueprints and routes, to bring alpha.com as a blueprint and when user logs in they are served alpha.com/app?

How can I modify the routes for alpha.com to avoid a collision with beta.com?

like image 854
Bob Jordan Avatar asked Aug 19 '17 04:08

Bob Jordan


People also ask

Can Flask serve multiple requests?

Improve performance in both Blocking and Non-Blocking web servers. Multitasking is the ability to execute multiple tasks or processes (almost) at the same time. Modern web servers like Flask, Django, and Tornado are all able to handle multiple requests simultaneously.

How do I run multiple Flask applications from the same server?

Enter application dispatching. With this you can combine multiple Flask applications at the WSGI level. This also allows you to combine any WSGI application. So if you have separate Flask, Django, or Dash applications you can run them in the same interpreter side by side if you want.

How does Flask handle multiple concurrent requests?

Flask applications are deployed on a web server, either the built-in server for development and testing or a suitable server (gunicorn, nginx etc.) for production. By default, the server can handle multiple client requests without the programmer having to do anything special in the application.

How many users can Flask handle?

Flask will process one request per thread at the same time. If you have 2 processes with 4 threads each, that's 8 concurrent requests. Flask doesn't spawn or manage threads or processes.


1 Answers

I found it is not very well supported in flask's current stable release of Flask==0.12.2. It theoretically can be done to a degree with host_matching. But in my testing, the static routes were always broken.

However, the flask development version on master at the time of writing has merged a pull request that makes it a little easier. Doing a pip install git+git://github.com/pallets/flask.git will install Flask==0.13.dev0. Then, using the factory pattern to create the flask app, you can set host_matching=True and static_host=127.0.0.1:8000 in my case.

For me, my factory function looks like this:

def create_app(config_obj=None):
    """An application factory, as explained here:
    http://flask.pocoo.org/docs/patterns/appfactories/.
    :param config_object: The configuration object to use.
    """
    app = Flask(__name__, host_matching=True, static_host='127.0.0.1:8000')
    app.config.from_object(config_obj)
    register_extensions(app)
    register_blueprints(app)
    return app

Another thing needed to make this work is to modify your hosts and set the domains you'd like to reference in the host file. On windows, this is found at C:\Windows\System32\drivers\etc\hosts. At the bottom of the hosts file, I've modified as such:

# localhost name resolution is handled within DNS itself.
#   127.0.0.1       localhost
#   ::1             localhost
    127.0.0.1       alpha.local
    127.0.0.1       beta.local
    127.0.0.1       local

You need to run this solution behind a reverse proxy (NGINX on Linux or IIS on Windows) and set it up to forward the appropriate requests to alpha.local:8000 or beta.local:8000 in the case of this example. But, you will modify <subdomain>.local:<port> based on your actual need.

Another issue you will deal with is browser complaining about CORS requests, so you may need to set headers for Access-Control-Allow-Origin: * or specific domains like Access-Control-Allow-Origin: http://beta.local:8000. For development server, I found this helpful for CORS to allow font access:

@blueprint.after_app_request
def add_headers_to_fontawesome_static_files(response):
    """
    Fix for font-awesome files: after Flask static send_file() does its
    thing, but before the response is sent, add an
    Access-Control-Allow-Origin: *
    HTTP header to the response (otherwise browsers complain).
    """
    if not os.environ.get('APP_ENV') == 'prod':
        if request.path and re.search(r'\.(ttf|woff|woff2|svg|eot)$', request.path):
            response.headers.add('Access-Control-Allow-Origin', '*')
        return response

Note that for production, you must set your modified headers on your proxy (like NGINX or IIS) and the above function is not useful for produciton.

Last, with host_matching=True then the routes must be specified for hosts, example being below:

@blueprint.route('/about/', methods=['GET', 'POST'],
                 host='<string:subdom>.local:8000')
def about_app(**kwargs):
    """The about page."""
    return render_template('about.html')

If you do the routes as above, it is helpful to set the url_defualts in a function somewhere in your app, like below:

@blueprint.app_url_defaults
def add_subdom(endpoint, values):
    path = request.url_root
    if 'alpha.local' in path:
        g.subdom = 'alpha'
    elif 'beta.local' in path:
        g.subdom = 'beta'
    if current_app.url_map.is_endpoint_expecting(endpoint, 'subdom'):
        values['subdom'] = g.subdom

Good luck, this was not easy.

like image 128
Bob Jordan Avatar answered Oct 10 '22 06:10

Bob Jordan