在反向代理 (ProxyPass) 后面的 Mojolicious 中配置带有前缀的 URL [英] Configure URLs with prefix in Mojolicious behind Reverse Proxy (ProxyPass)

查看:31
本文介绍了在反向代理 (ProxyPass) 后面的 Mojolicious 中配置带有前缀的 URL的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一种可靠的方法来配置 Mojolicious 在/app 下的 Apache 反向代理后面运行,以便 url_for('/​​foo') 实际上返回 /app/foo 而不是 /foo (否则所有链接都会被破坏).

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).

文档 显示了/下所有内容的反向代理示例.但这不是我需要的,因为应用程序应该在/app 下.
ProxyPass/http://localhost:8080/ 转换为 ProxyPass/app http://localhost:8080/ 将导致问题为 /appcode> 前缀将从应用程序生成的所有 url 中丢失.

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.

文档还有一个重写部分,其中有一个例子一个 before_dispatch 钩子,它将获取请求 url 的第一部分并将其用作基础.这需要将前缀附加到 Apache 配置中的 ProxyPass url(ProxyPass/app http://localhost:8080/app/ 与尾部斜杠),这似乎没有提到页面,但也许它不需要是(移动第一部分并从路径到基本路径的斜线"),因为它很明显.这使得调用 http://localhost/app/page 成为可能,它变成了 http://localhost:8080/app/page('app' 被删除钩子),其中 url_for('/​​foo') 将返回 '/app/foo' (http://localhost/app/foo>),因此链接将是正确的(在 ProxyPass 规则中没有尾部斜杠,这将使 /apppage/foo).

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).

然而,在这个例子中,url 修改总是在生产模式下进行(if app->mode eq 'production').所以直接调用后端服务器 (http://localhost:8080/testpage) 将不再起作用,因为所有的 url 都会被破坏.

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.

所以我想,我会检查是否设置了 X-Forwarded-For 标头(通过 mod_proxy_http),这将始终为反向代理请求设置.由于 Apache mod_proxy 文档 提到这个标题可能已经在客户端请求中设置(并且最终包含多个值),我会首先从请求中删除它 - 因为发送此标头的客户端不应导致 url 基修改.

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 配置:

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>

挂钩 Mojolicious 启动():

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};
    }
});

这似乎有效...

问题:我的方法在现实世界"中会可靠地工作还是有缺陷?


通过反向代理请求根地址 (http://localhost:3000/app/) 总是会导致错误 404.所以我添加了两行以在这种情况下关闭尾部斜杠.由于我在文档中找不到,可能有更好的方法.


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.

推荐答案

我现在正在回答我自己的问题,因为我从那些会在他们的应用程序中放置硬编码前缀的人那里得到更多建议(不仅仅是这里)代码.很明显,手动为所有生成的 url 添加前缀不是解决方案.想象一下,同一应用程序的两个实例部署在同一台服务器上,一个在 /app1 下,另一个在 /app2 下.我的问题中建议的代码的全部意义在于,如果通过反向代理访问,应用程序可以正常工作并生成正确的 url,而不会中断直接发送到应用程序服务器的请求.开发人员会运行 Morbo,但硬编码的前缀会破坏它.另外,我在我的问题中至少犯了一个错误,但似乎没有人注意到.

I'm now answering my own question as I'm getting more suggestions (not just here) from people who would put a hard-coded prefix in their application code. It should be obvious that prefixing all generated urls manually isn't a solution. Just imagine two instances of the same application deployed on the same server, one under /app1 and the other under /app2. The whole point of the suggested code in my question is that the application works and produces correct urls if accessed through a reverse proxy without breaking requests going directly to the application server. A developer would run Morbo, but a hard-coded prefix would break that. Also, I made at least one mistake in my question, but nobody seems to have noticed.

我在问题中的示例中有太多斜线.Location 块的定义方式,对 /app 没有尾部斜杠的请求将失败.像这样写可能会更好:

I had too many slashes in my example in the question. The way the Location block is defined, requests to /app without trailing slash would fail. It might be better to write it like that:

<Location "/app">
...

接下来,我写道我检查了 X-Forwarded-For 标头,但实际上我检查了 X-Forwarded-Host.如果我还清除了该标题,那将不是问题,但我清除了 X-Forwarded-For.由于这个尴尬的错误,安全机制将不起作用,因此如果您在 localhost:3000 上使用应用程序服务器时设置此标头,应用程序将尝试修复被操纵的 url,即使它不应该这样做.

Next, I wrote that I check for the X-Forwarded-For header but I actually checked for X-Forwarded-Host. That wouldn't be a problem if I were also clearing that header but I cleared X-Forwarded-For instead. With that awkward mistake, the safety mechanism wouldn't work, so if you'd set this header while working with your application server at localhost:3000, the app would try to repair the manipulated url even though it's not supposed to do that.

应该是:

RequestHeader unset X-Forwarded-Host

示例:

ProxyPreserveHost On
<Location /app>
    ProxyPass http://localhost:3000/app
    RequestHeader unset X-Forwarded-Host
</Location>

ProxyPreserveHost 指令不是必需的,只要应用程序在任何地方都使用相对 url.如果应用程序想要生成绝对 url,例如 url_for('/​​page')->to_abs,则应启用 ProxyPreserveHost,否则外部客户端将获得 http://localhost:3000/app/page.

The ProxyPreserveHost directive isn't required as long as the application uses relative urls everywhere. If the application wants to generate an absolute url, for example url_for('/page')->to_abs, ProxyPreserveHost should be enabled, otherwise external clients would get http://localhost:3000/app/page.

当我写这个问题时,我在 Mojolicious 文档 并且,正如问题中所指出的,我想将它用于在 /app 下运行的应用程序.但是,我不想破坏 Morbo.该示例假设应用程序在反向代理后面运行时处于生产模式($app->mode),但在直接通过 Morbo 访问时不是,但我不想更改模式其他所有请求.

When I wrote that question, I saw the before_dispatch hook in the Mojolicious documentation and, as pointed out in the question, I wanted to use it for an application running under /app. However, I didn't want to break Morbo. The example assumes that the application is in production mode ($app->mode) while running behind a reverse proxy but not when access directly through Morbo, but I didn't want to change the mode for every other request.

这就是为什么我添加了一个条件来检查请求是否来自反向代理.由于此标头仅由 Apache 设置(由 mod_proxy_http 模块) 而不是 Mojo::Server::Morbo,它可以用作反向代理检测.连同清除 X-Forwarded-Host 的正确指令,我相信我的问题的答案是,它应该可靠地工作.

That's why I added a condition to check if the request came through a reverse proxy. As this header is only set by Apache (by the mod_proxy_http module) and not by Mojo::Server::Morbo, it can be used as reverse proxy detection. Together with the right directive to clear the X-Forwarded-Host, I believe the answer to my question would be that yes, that should work reliably.

(虽然最后一部分并不是绝对必要的,只要直接访问应用服务器仅限于开发人员.)

(Although that last part isn't strictly necessary as long as direct access to the app server is limited to the developer.)

为了说明为什么我在 Apache 配置中的 ProxyPass 行中添加了 /app 前缀,我想指出这种方法会操纵 url允许应用程序在给定的前缀下工作.另一个问题有人忘记在 Apache 配置中添加前缀,我写了一个 回答解释钩子的作用.

To show why I've added the /app prefix to the ProxyPass line in the Apache configuration, I'd like to point out that this approach manipulates the url to allow the application to work under the given prefix. There's another question of someone who forgot to add the prefix in the Apache configuration and I wrote an answer explaining what the hook does.

Morbo: localhost:3000
Apache reverse proxy: host.com/app or localhost/app

# Browser > Apache:
http://host.com/app
# Apache (ProxyPass http://localhost:3000/) > Mojolicious sees:
GET /
url_for '/test' = /test 
(or even //test if the hook pushes undef, see the other answer linked above)
# Apache (configured as described above) > Mojolicious sees:
GET /app
# Hook:
base = /app
request = /
url_for '/test' = /app/test 

通常,ProxyPass 指令中的本地目标参数不会有前缀,它就像 ProxyPass http://...:3000/.在这种情况下,应用程序不知道前缀,这就是为什么所有生成的 url 和链接都不完整的原因.

Normally, the local target argument in the ProxyPass directive would not have the prefix, it would just be something like ProxyPass http://...:3000/. In that case, the application doesn't know about the prefix, which is why all generated urls and links are incomplete.

这种方法要求您让 Apache 将前缀传递给应用程序服务器.应用程序不知道前缀,因此它不知道如何处理对 /app/page 的请求.这就是钩子的用武之地.它假定路径的第一级始终是前缀,因此它将 /app/page 转换为 /page 并方便地附加url base 的 /app 前缀,在生成 url 时使用,确保指向 /test 的链接实际上指向 /app/test.

This approach requires you to let Apache pass the prefix through to the application server. The application doesn't know about the prefix, so it wouldn't know what to do with a request to /app/page. This is where the hook comes in. It assumes that the first level of the path is always the prefix, so it turns /app/page into /page and it conveniently appends the /app prefix to the url base, which is used when generating urls, making sure that a link to /test actually points to /app/test.

显然,对于直接发送给 Morbo 的任何请求,不应进行此修改.

Obviously, this modification should not be done for any request sent directly to Morbo.

或者,可以由反向代理设置自定义请求标头,然后由钩子使用以生成工作 url.Mojolicious::Plugin::RequestBase 模块就是这样工作的.它希望您在 X-Request-Base 标头中而不是在 url 中定义前缀:

Alternatively, a custom request header could be set by the reverse proxy and then used by the hook to produce working urls. The Mojolicious::Plugin::RequestBase module works that way. It expects you to define the prefix in the X-Request-Base header, not in the url:

RequestHeader set X-Request-Base "/app"

在这种情况下,应用程序应该只接收相对于该前缀的 url 请求:

In that case, the application should only receive requests for urls relative to that prefix:

ProxyPass http://localhost:3000/

该模块真正做的就是获取标题并将其用作网址基础:

All that module really does is pick up the header and use it as url base:

$c->req->url->base($url); # url = X-Request-Base = /app

示例:

<Location /app>
    ProxyPass http://localhost:3000
    RequestHeader set X-Request-Base "/app"
</Location>

这是一个很好且简单的解决方案.请注意,在这种情况下,/app 前缀出现了两次.当然,钩子由该模块仅在设置了 X-Request-Base 标头时才起作用,就像上面显示的钩子如果 X-Forwarded-Host未设置标头.

This is a nice and simply solution. Note that the /app prefix appears twice in that case. And of course, the hook implemented by that module only does its work if the X-Request-Base header is set, just like the hook shown above does nothing if the X-Forwarded-Host header is not set.

这篇关于在反向代理 (ProxyPass) 后面的 Mojolicious 中配置带有前缀的 URL的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆