When using url_for with _external argument set to "True", the returned url does not contain the port which defaults the url to port 80.
I have read a few posts regarding this and it was because people were not specifying the full "SERVER_NAME" but I didn't set it.
At first, I was creating an app context to use url_for but it told me I had to set my server name but I can't set it as it might be dynamic upon a user configuration.
Anyhow, what I did was to create an app context and create a test_request context to use url_for which gave me the whole URL without the port.
To clarify, this happens when listening to a different port like 5000?
I am not sure what the problem is you're describing. Do you have a testcase of what is broken?
Sorry if I didn't make myself clear enough. Let's say, I am running a webserver on the following host:port: 192.168.1.4:5000
app.run(host=192.18.1.4, port=5000)
Basically, if I use url_for(endpoint='settings'), it will return the relative url for that given endpoint:
print url_for('settings')
>> /settings
Now, what I would like is to return the full external URL such as: http://192.168.1.4:5000/settings and I happen to understand the _external parameter is exactly for what I try to achieve. But, from my experience, it would return:
print url_for('settings', _external=True)
>> http://192.168.1.4/settings
Which, in my case, is not the valid external URL because it is missing the port, or, more precisely, Flask appears to assume it always run on port 80 and does not take into account the application's port (here: 5000)
What I assumed, it should return:
print url_for('settings', _external=True)
>> http://192.168.1.4:5000/settings
Now, I also url_for may use SERVER_NAME (and not **SERVER_PORT??) when _external is set to True so that anyone can set the SERVER_NAME (i.e. 192.168.1.4:5:5000) and then, my expected external URL would be correct.
But, in my case, this is a standalone webserver and user can define whichever host/port they want and although I could make it so that my code returns and update the flask configuration dict, I woud like to believe, when there is an issue/bug here:
Note: I haven't tested all the usecase when using SERVER_NAME/PORT so it might works that way already but it didn't appear so.
The first usecase is the one I am raising this issue about and is surely not working that way although it might be intended by you but then, I would be grateful to have some clarification/explanations! Thank you:
As far as I can see, I don't have this problem (with version 0.10.1), url_for('thingy', _external=True) adds the correct port / host set in app.run
Yeah, me neither. Downgrading Werkzeug to 0.5 didn't introduce this issue either.
Hey, just seeing this thread now while trying to tackle a similar problem in a python/flask/_celery_ instance. In my issue (which @celestian02 may or may not have neglected to mention as part of his issue - either way, the following still describes _my_ issue from here on out) I was trying to add URLs to emails that were being sent by a celery task. For emails sent from within the live app, using url_for('thingy', _external=True) works just fine. No surprise there. But with no context of the app present in a celery task (even after including an import app from my_app_module) I was getting errors.
This answer (http://stackoverflow.com/a/16222783/1427426) got me on to the idea of running "in test request context" and using _external=True to get the absolute URL. I also needed the _PORT_ to show up, but it wasn't. This article (https://julo.ch/blog/why-flask-can-suck/) helped me think about how to get it to add the _PORT_ to this, which is also how I found this question we are reading now. Here's the code that I used to solve this problem:
# In some module of mine, let's call it `my_utils`
from my_app_package import app # Flask app
from my_app_config import PORT # The port I know I'm on - you can even do some logic to figure out which it is/should be. This should probably be available via Flask, however?
from flask import url_for # What we're trying to get t work
def my_url_for(*args, **kwargs):
with app.test_request_context():
# With the test request context of the app
kwargs['_external'] = True # Add the _external=True keyword argument to whatever else was passed
url = url_for(*args, **kwargs) # Get the URL
if PORT is not None: # If we have a port, we should do something
url = url.replace('://localhost/', '://localhost:%d/' % (PORT)) # Oh yeah, add the port to the URL!
# I only need to add the port to when I'm running locally, but you could use a regex here to add the port to any servername, if you have that type of setup
return url
This then allows me to use this in the following way:
# In my celery task definitions
from my_utils import my_url_for
... # more imports
def my_celery_task():
message = '<a href="%s">Click here!</a>' % (my_url_for('some_endpoint', arg1=val1, arg2=val2))
send_email(message)
This creates URLs with the hostname and port!
Here to comment because @newhouse's solution, while it appears to work, does create a hacked version of url_for which smells nasty. In my python/flask/celery environment, as long as I'm also providing an app.config['SERVER_NAME'] with the port in my development environment, url_for within app.test_request_context() in celery tasks behaves properly and has the correct port:
# config_local.py
...
SERVER_NAME = 'localhost:5000'
# tasks.py
from my_app_package import app, celery
@celery.task
def my_task():
with app.test_request_context('/path'):
url = url_for('module.route', _external=True) # returns 'http://localhost:5000/module/route'
Definitely a bit of a windy road to sort this out for celery users, test cases, or anything that runs afoul of the assumption that anything will go through app.run() and provide a port
Here's a solution I came up with to fix a similar issue I had with passing _external to url_for, in this case to get it to use the same scheme as my application. It's basically similar to the above suggestion, but I think it's a little cleaner (because it doesn't rely on replace) & could be extended for port issues, etc.
Also as my function name suggests, it's not my_url_for, it's external_url_for, because flask hasn't provided us with a better way to actually get that without patching it. Slightly less code smell, if you change how you think about it.
def external_url_for(view, **kwargs):
""" Takes a view function name, like `flask.url_for`,
and returns that view function's URL prefixed
with the full & correct scheme and domain. """
parsed_url = urlparse(
url_for(view, _external=True, **kwargs))
app_scheme = urlparse(
current_app.config['HOST']).scheme
final_parsed_url = parsed_url._replace(scheme=app_scheme)
return final_parsed_url.geturl()
P.S. I realize this doesn't tackle the app/request context issues. I didn't need to use it in celery tasks.
Most helpful comment
Sorry if I didn't make myself clear enough. Let's say, I am running a webserver on the following host:port: 192.168.1.4:5000
Basically, if I use url_for(endpoint='settings'), it will return the relative url for that given endpoint:
Now, what I would like is to return the full external URL such as: http://192.168.1.4:5000/settings and I happen to understand the _external parameter is exactly for what I try to achieve. But, from my experience, it would return:
Which, in my case, is not the valid external URL because it is missing the port, or, more precisely, Flask appears to assume it always run on port 80 and does not take into account the application's port (here: 5000)
What I assumed, it should return:
Now, I also url_for may use SERVER_NAME (and not **SERVER_PORT??) when _external is set to True so that anyone can set the SERVER_NAME (i.e. 192.168.1.4:5:5000) and then, my expected external URL would be correct.
But, in my case, this is a standalone webserver and user can define whichever host/port they want and although I could make it so that my code returns and update the flask configuration dict, I woud like to believe, when there is an issue/bug here:
Shouldn't the url_for return: http://host:port/endpoint_relative_url
Shouldn't the url_for return: **http://SERVER_NAME:SERVER_PORT/endpoint_relative_url
Note: I haven't tested all the usecase when using SERVER_NAME/PORT so it might works that way already but it didn't appear so.
The first usecase is the one I am raising this issue about and is surely not working that way although it might be intended by you but then, I would be grateful to have some clarification/explanations! Thank you: