I'm looking for a reliable way to configure Mojolicious running behind an Apache reverse proxy under /app, so that url_for('/foo')
actually returns /app/foo
instead of just /foo
(otherwise all the links would be broken).
The documentation shows a reverse proxy example for everything under /. But that's not what I need, since the application should be under /app.
Turning ProxyPass / http://localhost:8080/
into ProxyPass /app http://localhost:8080/
will cause the issue as the /app
prefix would be missing from all urls generated by the application.
The documentation also has a section on rewriting, which has an example of a before_dispatch hook that will take the first part of the request url and use it as base. This requires the prefix to be appended to the ProxyPass url (ProxyPass /app http://localhost:8080/app/
with trailing slash) in the Apache config, which does not seem to be mentioned on that page but maybe it does not need to be ("Move first part and slash from path to base path") because it's obvious. That makes it possible to call http://localhost/app/page
, which turns into http://localhost:8080/app/page
('app' removed by the hook), where url_for('/foo')
will return '/app/foo'
(http://localhost/app/foo
), so the links will be correct (without trailing slash in the ProxyPass rule, that would make /apppage/foo
).
However, in this example, the url modification is always made in production mode (if app->mode eq 'production'
). So calling the backend server directly (http://localhost:8080/testpage
) won't work anymore, since all the urls would be broken.
So I thought, I'd check if the X-Forwarded-For
header is set (by mod_proxy_http) instead, which would always be set for reverse proxy requests. And since the Apache mod_proxy documentation mentions that this header might already be set in the client request (and end up containing more than one value), I'd remove it from the request first - because a client sending this header should not cause the url base modification.
Apache VirtualHost configuration:
# ProxyPreserveHost: Mojo::URL::to_abs() not 127.0.0.1
ProxyPreserveHost On
<Location "/app/">
# ProxyPass: prefix pass-thru
ProxyPass http://localhost:3000/app/
# RequestHeader: must not be set externally
RequestHeader unset X-Forwarded-For
</Location>
Hook in Mojolicious startup():
$self->hook('before_dispatch' => sub {
my $c = shift;
my $behind_proxy = !!$c->req->headers->header('X-Forwarded-Host');
if ($behind_proxy) {
push @{$c->req->url->base->path->trailing_slash(1)},
shift @{$c->req->url->path->leading_slash(0)};
$c->req->url->path->trailing_slash(0) # root 404
unless @{$c->req->url->path->parts};
}
});
This seems to work...
Question: Will my approach work reliably in the "real world" or is it flawed?
Edit:
Requesting the root address (http://localhost:3000/app/
) through the reverse proxy always resulted in an error 404. So I've added two lines to turn the trailing slash off in that case. Since I can't find that in the docs, there may be a better way.