Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

url_for produces relative links with nontrivial APPLICATION_ROOT #3219

Closed
rosekunkel opened this issue May 27, 2019 · 8 comments
Closed

url_for produces relative links with nontrivial APPLICATION_ROOT #3219

rosekunkel opened this issue May 27, 2019 · 8 comments

Comments

@rosekunkel
Copy link

rosekunkel commented May 27, 2019

Expected Behavior

url_for should consistently produce urls which are absolute with regard to the server root, i.e., they should start with a /.

app = Flask(__name__, static_url_path='')
url_for('static', filename='some_static_file')

Actual Behavior

When running under gnunicorn with APPLICATION_ROOT='/myapp/', the url_for call produces the URL myapp/some_static_file, while when running under werkzeug with APPLICATION_ROOT='/', the url_for call produces the URL /some_static_file.

This is a problem, because, combined with this werkzeug issue, there's no straightforward way to use url_for in templates for both development and production: {{ url_for('static', filename='some_static_file') }} produces myapp/some_static_file in production, which fails, while /{{ url_for('static', filename='some_static_file') }} produces //some_static_file in development, which fails.

Environment

  • Python version: 3.6.5
  • Flask version: 1.0.2
  • Werkzeug version: 0.14.1
@davidism
Copy link
Member

APPLICATION_ROOT, along with SERVER_NAME, is only used when generating URLs outside a request (such as generating external URLs for an email). It appears to still be working as expected.

app.config["SERVER_NAME"] = "example.com"
app.config["APPLICATION_ROOT"] = "/app"

with app.app_context():
    print(url_for("static", filename="test.txt", _external=True))
    # http://example.com/app/static/test.txt

If you're using Gunicorn to serve a Flask app under a non-root path, you need to configure Gunicorn to set the WSGI SCRIPT_NAME correctly. One way to do this is to set the SCRIPT_NAME environment variable.

SCRIPT_NAME=/app gunicorn -b 127.0.0.1:5000 -w 2 example:app

If you're using the dev server, you can use DispatcherMiddleware to configure the SCRIPT_NAME in a similar way. You can also use this with Gunicorn, if you don't want Gunicorn to handle SCRIPT_NAME itself.

dev_app.py:

from werkzeug.exceptions import NotFound
from werkzeug.wsgi import DispatcherMiddleware
from myproject import app

app.wsgi_app = DispatcherMiddlware(NotFound(), {"/app": app.wsgi_app})
export FLASK_APP=dev_app.py
export FLASK_ENV=development
flask run

@davidism
Copy link
Member

#2759 is open for better documentation about this.

@bsolomon1124
Copy link

Not sure if gevent as wsgi server supports SCRIPT_NAME. For some reason it looks to simply set that value empty.

@davidism
Copy link
Member

davidism commented Sep 2, 2020

I'm not sure what part of Gunicorn that is, but it does indeed support SCRIPT_NAME, as shown above. How it picks it up must be happening elsewhere in the code. https://docs.gunicorn.org/en/latest/faq.html?highlight=script_name#how-do-i-set-script-name

@bsolomon1124
Copy link

bsolomon1124 commented Sep 2, 2020

@davidism I am actually talking about using gevent as standalone wsgi app server (as you allude to, it is more commonly used as a gunicorn worker class):

from flask import Flask, redirect, url_for
from gevent.pywsgi import WSGIServer

app = Flask(__name__)

@app.route('/bar')
def hello_world():
    return "hi"

@app.route("/favicon.ico")
def favicon():
    return redirect(url_for("static", filename="favicon.ico"))

if __name__ == "__main__":
    WSGIServer(('127.0.0.1', 5000), application=app).serve_forever()
$ SCRIPT_NAME='/foo' python hello.py

In this case url_for("static", filename="favicon.ico") will still generate the absolute path /static/favicon.ico. If the app is reverse-proxied by something like nginx which proxy-passes /foo to gevent, that path should be /foo/static/favicon.ico.

It is the end of a long day and I am probably not thinking about this the right way, though.

@davidism
Copy link
Member

davidism commented Sep 2, 2020

Oh, that's gevent. This sounds like an issue with gevent then, or with your setup of gunicorn and gevent. An old closed issue on the Flask issue tracker is not the best place to get answers about that. 😉

@bsolomon1124
Copy link

@davidism Please let me give another example here.

Say that a template contains

<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">

In this case (using example above) you have href="/static/favicon.ico" in HTML with no other contextual information for the browser to go off of. If your reverse proxy server is proxy-passing requests to www.host.example.com/foo to your application's root, then this href should be /foo/static/favicon.ico. That seems to be independent of gunicorn vs gevent, no? Taking gunicorn as an example, could you help me understand through what mechanism SCRIPT_NAME makes its way into url_for()?

@davidism
Copy link
Member

davidism commented Sep 2, 2020

When Flask.url_map is bound to the current request.environ, it will append SCRIPT_NAME to any URL it generates. Whether SCRIPT_NAME is set is not something Flask controls, only uses.

Please use Stack Overflow or https://discord.gg/pallets for questions about your own code. This tracker is for issues related to the project itself

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants