-
-
Notifications
You must be signed in to change notification settings - Fork 931
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
Starlette behind reverse proxy, url_for does not return externally valid urls #604
Comments
Forgive my haste, this is a possible duplicate of or at least related to #592 |
See uvicorn's |
I am aware of those configuration settings, the scheme and host works fine, but the resulting URL is still not correct. Example: from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
async def index(request):
return PlainTextResponse(request.url_for('index'))
app = Starlette()
app.add_route('/', index, methods=['GET']) I use Traefik to load-balance and use the following rules to direct the incoming connections to the Starlette application: Accessing |
The Uvicorn documentation says that Looking at the proxy-headers middleware, it indeed only checks those two headers.
|
I looked into this further, the problem comes from [calling make_absolute_url][1], which uses the connexion's base URL. That base URL does correct itself to account for root_path, but then [make_absolute_url only copies the netloc][2] from that base URL instead of combining the two. Also, while class ProxyHeadersMiddleware2:
def __init__(self, app, num_proxies=1):
self.app = app
self.num_proxies = num_proxies
async def __call__(self, scope, receive, send):
if scope["type"] in ("http", "websocket"):
headers = dict(scope["headers"])
if b"x-forwarded-host" in headers:
x_forwarded_host = headers[b"x-forwarded-host"].decode("ascii")
(host, sep, port) = x_forwarded_host.partition(":")
if sep is None:
scope["server"] = (host, 0)
else:
scope["server"] = (host, int(port))
await self.app(scope, receive, send) ...it makes no difference as far as starlette/starlette/requests.py Line 126 in 6fa0038
[2]: starlette/starlette/datastructures.py Lines 183 to 188 in 6fa0038
[3]: starlette/starlette/datastructures.py Lines 32 to 42 in 6fa0038
|
Indeed - good catch. |
@tomchristie: Excellent, though that doesn't fix setting the host/scheme configuration (the scheme appears to be preserved correctly, but the host isn't affected). Your advice made it seem like preserving the user-facing netloc was possibly maybe part of the intended side effects, and if it was, then that should probably be raised on Uvicorn's side (unless that's something Starlette is expected to take care of, there doesn't seem to be a concensus on what the right option for this is). There's also the possibility of just serving absolute paths instead of URLs, which would circumvent the issue in most cases. |
Thanks, yup good follow up.
So - I'd suggest opening an issue against Uvicorn there. As part of that it'd be helpful if you could review exactly what Gunicorn's handling behavior is. I think there's actually quite a range of not-entirely-standardized behaviours for what proxy headers are and aren't used. We should get the common case sorted in Uvicorn (basically let's just stay in line with whatever Gunicorn does) and leave anything more complex/custom for custom middleware to handle. We probably also want to document all that in Uvicorn, including handling custom case.
Yeah, I think we may want to do that, or else at least review it. I'm also fairly sure that we ought to be adding some API for keeping app and request context, so that you can call |
@tomchristie: I was going to post the following as a uvicorn issue, but as I'm typing this out, I realize that I have no idea how to wrap this up into a Uvicorn issue:
If Uvicorn is meant to be some kind of "ASGI drop-in replacement for Gunicorn" of sorts, then this would suggest that it should not handle proxy headers at all and that this responsibility should possibly be left up to the application framework or to some kind of middleware. The ASGI protocol says I would definitely prefer this to be an HTTP server responsibility, but it looks like proxies (in regard to the application knowing what hostname it should use for URLs) are "somebody else's problem" everywhere you look. |
I am confused as to why this, and #592 , have both been closed, @tomchristie - do you regard this as something that should not be fixed in Starlette? |
I really just think Starlette shouldn't be trying to generate URLs with authority, here. The rule seems to be that, unless the authority you want to use is known to you in advance (such as having it stored in an environment variable or a config file), you should just specify the URL as an absolute path and leave it at that. It's what Flask's Trying to automatically build URLs with authority using the routing headers like that is just going to lead to issues. One should still be able to build authority-form URLs, but it shouldn't be the default. |
Suppose I have my application reverse proxied, such that http://my.domain/foo (e.g. Traefik or Nginx) has the upstream http://localhost:8000 (the Starlette app).
url_for
will generate urls relative to the upstream url. How do I make it produce urls relative to the external url (with prefix /foo)?For other projects, e.g. Flask/Werkzeug, this can be handled by setting
SCRIPT_NAME
in the request environment (scope) if the 'X-Forwarded-Prefix' header is set. It does not look like there is an equivalent option in Starlette.Am I missing something? Otherwise, consider this a feature request.
The text was updated successfully, but these errors were encountered: