在反向代理(ProxyPass)后面配置Mojolicious中带有前缀的URL

bas*_*ic6 5 apache perl reverse-proxy url-rewriting mojolicious

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

文档显示了/下所有内容的反向代理示例.但这不是我需要的,因为应用程序应该在/ app下.
谈及ProxyPass / http://localhost:8080/ProxyPass /app http://localhost:8080/会造成问题,因为/app前缀会从应用程序生成的所有URL会丢失.

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

但是,在此示例中,URL修改始终在生产模式(if app->mode eq 'production')中进行.所以直接调用后端服务器(http://localhost:8080/testpage)将不再起作用,因为所有的URL都会被破坏.

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

Apache VirtualHost配置:

# 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>
Run Code Online (Sandbox Code Playgroud)

在Mojolicious启动中的钩子():

$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};
    }
});
Run Code Online (Sandbox Code Playgroud)

这看似有效......

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

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

bas*_*ic6 3

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

代码中的缺陷

我在问题中的示例中有太多斜杠。Location根据块的定义方式,/app不带尾部斜杠的请求将会失败。这样写可能会更好:

<Location "/app">
...
Run Code Online (Sandbox Code Playgroud)

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

本来应该是:

RequestHeader unset X-Forwarded-Host
Run Code Online (Sandbox Code Playgroud)

例子:

ProxyPreserveHost On
<Location /app>
    ProxyPass http://localhost:3000/app
    RequestHeader unset X-Forwarded-Host
</Location>
Run Code Online (Sandbox Code Playgroud)

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

反向代理检测

当我写这个问题时,我before_dispatchMojolicious 文档中看到了这个钩子,正如问题中指出的,我想将它用于在/app. 然而,我并不想破坏莫尔博。该示例假设应用程序在$app->mode反向代理后面运行时处于生产模式 ( ),但直接通过 Morbo 访问时则不然,但我不想更改每个其他请求的模式。

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

(尽管只要开发人员直接访问应用程序服务器,最后一部分并不是绝对必要的。)

被操纵的网址

为了说明为什么我将/app前缀添加到ProxyPassApache 配置中的行中,我想指出此方法会操纵 url 以允许应用程序在给定前缀下工作。还有一个问题是有人忘记在 Apache 配置中添加前缀,我写了一个答案来解释该钩子的作用

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 
Run Code Online (Sandbox Code Playgroud)

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

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

显然,对于直接发送到 Morbo 的任何请求都不应该进行此修改。

选择

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

RequestHeader set X-Request-Base "/app"
Run Code Online (Sandbox Code Playgroud)

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

ProxyPass http://localhost:3000/
Run Code Online (Sandbox Code Playgroud)

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

$c->req->url->base($url); # url = X-Request-Base = /app
Run Code Online (Sandbox Code Playgroud)

例子:

<Location /app>
    ProxyPass http://localhost:3000
    RequestHeader set X-Request-Base "/app"
</Location>
Run Code Online (Sandbox Code Playgroud)

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