pallets / flask

The Python micro framework for building web applications.
https://flask.palletsprojects.com
BSD 3-Clause "New" or "Revised" License
68.02k stars 16.21k forks source link

url_for to another application during request? #1412

Closed a-urth closed 9 years ago

a-urth commented 9 years ago

Assuming I have more than one Flask application (I'm using blueprints too, so there is not need to advice me them) and I want to build url from one to another. With no request context I was just using forced context of target application and it worked. But what should I do with existing request context? If i receive request to one application and want to do redirect to another one?

Or, in another words, why url_for first of all searches for request context? Maybe it should be optional, through _external parameter for example?

Please, do not send me to stackoverflow, my question about similar problem is hanging for more than a week with no responses at all.

untitaker commented 9 years ago

url_for searches in the request context because e.g. if an absolute URL is wanted, it will take a look at the current request to check whether the resulting URL should be HTTPS or HTTP.

This kind of entanglement of multiple apps is not officially supported. I'm not sure if the request context is even hindering you while generating the URL, but if it is, you can manually pop the request context from the stack, and push it after you generated the URL. While happening all within the Python interpreter, this is quite costly, and I think it's much smarter to generate your URLs ahead of time.

The _external parameter already exists, it controls whether absolute URLs should be generated.

a-urth commented 9 years ago

Popping request context just for url generation seems like a hack, Its easier for me to write my own url generator with possibility to choose which context should be used or, in another words, absolute or relative url should be built.

I know that _external parameter controls absolute url behaviour, so maybe it should be responsible for request-application context determination too?

My motivation is that it seems not logical that if You want to built url to another application within request context, You have no clear way how to do it.

untitaker commented 9 years ago

This kind of usage is simply not supported, so yes, this is going to require hacks (or a new URL generator) on your side.

Application dispatching is of course supported, but not the kind of combination you want: Poking at an application's internal state from another one.

a-urth commented 9 years ago

This looks really strange to me, especially that whole application context thing is designed for multi application usage. Or, I just missing something.

Anyway, thanks, I'll think further on this problem.

RonnyPfannschmidt commented 9 years ago

shouldnt entering a request context for the other application be suficient (in case both are served form the same process)

a-urth commented 9 years ago

I don't clearly understand Your proposition, what do You mean entering context for other application? And whats the point with the process?

By the way, there is even deeper problem - I'm using dispatcher middleware, and my applications dispatched by url prefix. And application url map has nothing to do with this prefix. So, actually from application perspective its simply impossible to built correct url to another application. I ended up creating separated url builders for each application.

RonnyPfannschmidt commented 9 years ago

what exactly are your different applications?

a-urth commented 9 years ago

Different Flask instances dispatched with DispatcherMiddleware

RonnyPfannschmidt commented 9 years ago

so its the same app? is the database different?

a-urth commented 9 years ago

Ok, how is database related to how many applications do I have? App in terms web application - Yes, its same. App in terms flask.Flask application instances - than No, currently i have 4 of them, each one responsible for separate part of the project.

patrickyan commented 9 years ago

I am also trying to do this as well. I have separate "applications" dispatched using SubdomainDispatcher. I want to link from one subdomain app to another, but url_for() fails to build the urls. Currently, my only solution is to manually type the urls.

patrickyan commented 9 years ago

I can give you a reason/example. I have an admin/employee app and a user app. The models etc. are shared, but the applications are under different subdomains for cookies, easy management, hosting, etc. reasons. In the user app, there may be actions that trigger emails to an admin with links to pages in the admin app. Currently, this must be done manually. It would be ideal to be able to do something like url_for('mybp.myroute', _external=True, app=myadminapp).

asteinlein commented 8 years ago

Just came across this same requirement myself, similar to the last commenter's example. Has anyone found a good workaround for this? Would Flask be interested in having this supported somehow? Seems pretty useful to me in a framework built for multi-app support.

davidism commented 8 years ago

1548

Vitalium commented 8 years ago

Hi all!

Why this issue is closed? As I can see, there are nor solution neither workaround. According to this advice for proper url generation there are must be set APPLICATION_ROOT (for correct url_prefix) and SERVER_NAME (for correct absolute URLs) and call url_for like this:

    with app.app_context(), app.test_request_context():
        return url_for(endpoint, **values)

where app is destination

untitaker commented 8 years ago

Yes, this kind of usage is simply not supported by Flask.

anthonyjb commented 7 years ago

In case it's useful I've used this approach to allow url_for to access endpoints against other apps:

from flask import current_app, url_for
from werkzeug.wsgi import DispatcherMiddleware

__all__ = ['Dispatcher']

class Dispatcher:
    """
    Allows one to mount middlewares or applications in a WSGI application.

    This is useful if you want to combine multiple WSGI applications::

        app = DispatcherMiddleware(app, {
            '/app2':        app2,
            '/app3':        app3
        })

    """

    def __init__(self, app, mounts=None):
        self.app = app
        self.mounts = mounts or {}
        self.url_for_resolver = URLForResolver(
            [self.app] + list(self.mounts.values())
            )

    def __call__(self, environ, start_response):
        script = environ.get('PATH_INFO', '')
        path_info = ''

        while '/' in script:
            if script in self.mounts:
                app = self.mounts[script]
                break
            script, last_item = script.rsplit('/', 1)
            path_info = '/%s%s' % (last_item, path_info)
        else:
            app = self.mounts.get(script, self.app)

        original_script_name = environ.get('SCRIPT_NAME', '')
        environ['SCRIPT_NAME'] = original_script_name + script

        # Convert empty path info values to a forward slash '/'
        environ['PATH_INFO'] = path_info or '/'

        return app(environ, start_response)

class URLForResolver:
    """
    A URL resolver that provides resolution of `url_for` across multiple apps.
    """

    def __init__(self, apps):
        self.apps = apps
        self.cache = {}

        for app in apps:
            app.url_build_error_handlers.append(self)

    def __call__(self, error, endpoint, values):
        """Attempt to resolve a URL any of the registered apps"""

        # Check if we have a cached look up
        if endpoint in self.cache:
            app = self.cache[endpoint]
            if app:
                with app.app_context(), app.test_request_context():
                    return url_for(endpoint, **values)
            else:
                raise error

        # Attempt to find an app with the registered endpoint
        for app in self.apps:

            # No point in checking the current app
            if app is current_app:
                continue

            for rule in app.url_map.iter_rules():

                if rule.endpoint == endpoint:
                    # Found - cache the result and call self to return the URL
                    self.cache[endpoint] = app
                    return self(error, endpoint, values)

        # Not found - cache the result and re-raise the error
        self.cache[endpoint] = None
        raise error
jamiejackherer commented 4 years ago

In case it's useful I've used this approach to allow url_for to access endpoints against other apps:

@anthonyjb How is this actually implemented?