【问题标题】:How do I set the base URL for my proxied app?如何为我的代理应用设置基本 URL?
【发布时间】:2015-10-30 03:18:58
【问题描述】:

我需要在 Apache 反向代理后面放置一个 Mojolicious 应用程序。在代理后面时,我无法让 Mojolicious 生成有效的 URL。

我正在使用 Mojolicious 6.14 和 Perl 5.18.1。

这是我根据https://github.com/kraih/mojo/wiki/Apache-deployment(在路径部分)设置的 Apache 反向代理配置。

<Proxy *>
  Order deny,allow
  Allow from all
</Proxy>
ProxyRequests Off 
ProxyPreserveHost On

ProxyPass /app1 http://localhost:3000/ keepalive=On
ProxyPassReverse /app1 http://localhost:3000/

RequestHeader set X-Forwarded-HTTPS "0" 

这是我的测试用例。

use 5.014;
use Mojolicious::Lite;

app->hook('before_dispatch' => sub {
  my $self = shift;
  if ($self->req->headers->header('X-Forwarded-Host')) {
    #Proxy Path setting
    my $path = shift @{$self->req->url->path->parts};
    push @{$self->req->url->base->path->parts}, $path;
  }
});

any '/' => sub {
    my $c = shift;
    $c->render('index');
};

any '/test' => sub {
    my $c = shift;
    $c->render('test');
};

app->start;

__DATA__

@@ index.html.ep

<!DOCTYPE html>
<html>
<head><title>Index Page</title></head>
<body>
  <p>Index page</p>
  <p>
  %= link_to 'Go to Test Page' => '/test'
  </p>
</body>
</html>

@@ test.html.ep
<!DOCTYPE html>
<html>
<head><title>Test Page</title></head>
<body>
  <p>Test page</p>
  <p>
  %= link_to 'Return to home page' => '/'
  </p>
</body>
</html>

访问http://www.example.com/app1可以看到索引页面,但是测试页面的链接不正确。链接是//test,而我预计它是http://www.example.com/app1/test

这是测试用例的 HTML 输出。

<!DOCTYPE html>
<html>
<head><title>Index Page</title></head>
<body>
  <p>Index page</p>
  <p>
  <a href="//test">Go to Test Page</a>
  </p>
</body>
</html>

我如何告诉 Mojolicious 我的应用的基本 URL 是什么,以便它生成正确的链接?

【问题讨论】:

标签: perl mojolicious


【解决方案1】:

可能需要在 apache 配置中替换 http://localhost:3000/app1 上的服务器代理传递:

ProxyPass /app1 http://localhost:3000/app1 keepalive=On
ProxyPassReverse /app1 http://localhost:3000/app1

【讨论】:

  • 你在正确的轨道上!不幸的是,您还没有解释为什么需要这样编写才能使这个特定的钩子起作用。在其他情况下可能会有所不同。请参阅我的answer 以获得解释。
【解决方案2】:

某种解决方法,但这就是我解决此问题的方法:

在配置文件 (.conf) 中,我定义了基本 URL:

base_url => 'https://booking.business-apartments.wien',

这让我可以编写这样的模板:

%= link_to 'Payment Information' => ( config('base_url') . url_for('intern/invoice/list_payments/') );

【讨论】:

    【解决方案3】:

    这是个好问题!从这里和其他地方的一些答案来看,似乎普遍缺乏对 Apache 反向代理设置如何影响 Mojolicious 应用程序以及钩子应该做什么的理解。

    您收到的answer 基本上是正确的,但它以“可能 [需要替换服务器代理通行证...”开头,并且没有提供任何解释。试错法可能对您有用,也可能对您无效。如果你的钩子工作方式不同,它可能不会。

    Apache 反向代理

    这是您的反向代理配置(删除了斜杠,见下文):

    ProxyPass /app1 http://localhost:3000 keepalive=On
    

    引用Apache documentation:

    假设本地服务器地址为http://example.com/;那么

    | ProxyPass /mirror/foo/http://backend.example.com/

    将导致对http://example.com/mirror/foo/bar 的本地请求在内部转换为对http://backend.example.com/bar 的代理请求。

    现在,假设您的 Apache 正在侦听 localhost:80 并且您的 (Morbo) 应用程序服务器正在侦听端口 3000,则 Apache 接收到对 http://localhost/app1 的请求并作为 / 转发到您的应用程序。 app1 前缀已丢失,这就是基本 url 中缺少它的原因,即所有链接中都缺少它。要修复应用程序生成的 url,必须将此前缀添加到基本 url,这将我们引向钩子。

    挂钩

    这是你的钩子函数:

    if ($self->req->headers->header('X-Forwarded-Host')) { # 1. if
        my $path = shift @{$self->req->url->path->parts}; # 2. shift
        push @{$self->req->url->base->path->parts}, $path; # 3. push
    }
    

    这个钩子应该修复基本 url。如上所述,app1 前缀需要添加到基本 url,它被添加到所有生成的 url。如果您的模板之一链接到 /test,则基本 URL 应类似于 /app1 以获取最终 URL /app1/test

    这就是你的钩子的作用:

    1. 通过检查 X-Forwarded-Host,您确保在请求来自反向代理时修改基本 url。这是有效的,因为mod_proxy_http module (documentation) 会自动设置该标题。如果没有该检查,您将无法直接在 localhost:3000 下访问您的应用程序服务器,所有 url 都会被破坏。

      其实我问的是question on how this distinction should be made in a reliable way to fix the url prefix when using a reverse proxy without breaking requests going to the application server。不幸的是,我收到的大多数答案都是错误的。但我相信检查 X-Forwarded-Host 已经足够好了,因为它是由 Apache 设置的,而不是由 Morbo 或 Hypnotoad 设置的。事实上,它是由反向代理设置的,这正是您要寻找的。​​p>

    2. 这个shift应该是从请求url中提取前缀。

      这是必要的,因为严格来说,将应用程序前缀附加到 ProxyPass 指令会操纵最终请求 url,因此您的应用程序会收到对 /app1/ 的请求。当然,该地址没有路由,因为您的应用程序中的路由器不知道 /app1 是该实例的前缀,而不是相对的应用程序 url。

      显然,如果您在/app2 下部署同一应用程序的另一个副本,则将硬编码前缀/app1 添加到所有模板(有些人可能会这样做)将不起作用。即使您不这样做,如果您的提供商强制您将 app1 前缀更改为 app_one,您仍然必须更改所有链接。这就是为什么在该钩子中提取前缀,存储以使链接工作(参见#3),然后从请求 url 中删除以使路由器满意。

    3. 这是/app1 前缀(单个路径标记)附加 到基本 url 的地方。基本 url 预先添加到您的模板中生成的 url。这就是将/test 变成/app1/test 的原因(如果请求来自反向代理)。

      在您的情况下,/test 变成了//test,因为您缺少前缀。我已经在这个答案的末尾解释了这一点。

    修复反向代理

    话虽如此,您的反向代理需要操纵请求 url 以包含前缀才能使钩子工作:

    ProxyPass /app1 http://localhost:3000/app1
    

    修改后,你的钩子就可以工作了:

    1. 仅当设置了反向代理标头时才修改基本 url,因为只有在使用反向代理时才需要修改。

    2. 所有发往 Mojolicious 应用程序的请求都将具有 /app1 前缀,例如 /app1/test。这一步去掉前缀,把url变成/test

    3. 将第2步中去掉的前缀附加到base url中,以后用来生成链接。

    这应该解释为什么您需要将应用程序前缀添加到ProxyPass 行。如果没有这种解释,其他人可能会尝试这样做而没有成功,因为他们可能有不同的钩子函数。

    斜线

    一个斜杠就可以破坏所有内容并导致大多数请求失败并出现错误 404。

    请注意,ProxyPass 行(第二个参数)中的本地目标 URL 有一个斜杠,但路径参数没有。如果这些不匹配,您最终可能会在请求 url 中出现双斜杠,并且某些请求可能会失败。

    来自Apache documentation

    如果第一个参数以 / 结尾,则第二个参数也应该以 / 结尾,反之亦然。否则,生成的对后端的请求可能会错过一些需要的斜线,并且无法提供预期的结果。

    现在,如果您删除尾部斜杠但忘记了前缀...

    ProxyPass /app1 http://localhost:3000
    

    ...生成的 url 仍然有两个前导斜杠:url_for '/test' = //test
    这是因为您将 undef 附加到要附加应用程序前缀的基本 url。

    发生的情况是,在第 2 步(见上文)中,您提取前缀,假设应用程序运行在文档根目录的下一级,即,您的前缀类似于 app1 而不是 apps/app1(在在这种情况下,移位/推送例程必须运行两次)。但是 ProxyPass 指令中没有前缀,因此您的应用程序会看到类似 / 的内容,换句话说,没有可以从 parts 中提取的内容。并且代码中也没有任何保护措施,因此您最终将 undef 推送到基本 url 的部件数组中。如果您随后生成一个 url,Mojolicious 会为该 undef 元素添加一个额外的斜线,这就是您得到 //test 的原因。零件数组如下所示:

    "parts" => [
      undef,
      "test"
    ],
    

    要修复这个双斜杠错误,您可以为钩子添加保护措施:

    my $path = shift @{$self->req->url->path->parts};
    if ($path) { # safeguard
        push @{$self->req->url->base->path->parts}, $path;
    }
    

    当然,只要您的反向代理配置中有前缀,就应该始终定义$path

    肯定有人会说这种方法是一种 hack,因为它会操纵 url。在某些情况下,黑客往往会失败。在这种情况下,如果您在直接访问应用程序服务器时手动设置X-Forwarded-Host,它将失败。我在my question 中提到过。但作为开发人员,您可能是唯一可以直接访问该应用程序服务器的人,因为在典型的生产环境中,防火墙只允许对反向代理的外部请求。那我就不说了。

    【讨论】:

      【解决方案4】:

      您是否应该尝试将您的 Mojolicious 更新到较新的版本?我记得我有一个类似的问题,我用一个代码解决了这个问题,我明确定义了代理的 url 并将其附加到每个请求(类似于 lanti 的答案)。在一些愉快的更新之后,代码不再需要了。 此外,我认为我使用的配置与 Logioniz 提出的相同。

      【讨论】:

      • 我对此投了反对票,因为它没有回答问题。这与 Mojolicious 版本无关。即使 Mojolicious 发生了相关变化,如果前缀从未传递给应用程序,应用程序如何知道前缀?
      • 代理前缀从服务器(Apache、Nginx)传递给应用程序。我只是记得,在某些旧版本中,此信息被忽略了,并且需要自定义实现(类似于您提供的@basic6)。更新后(不幸的是,我不记得是哪一个),情况不再如此,因此在发布问题时(2015 年)更新可能有所帮助。
      【解决方案5】:

      当你将你的应用挂载到某个点(不是 root / )时,你会好奇地开始运行它。看看 Mojolicious::Controller::url_for

        # Make path absolute
        my $base_path = $base->path;
        unshift @{$path->parts}, @{$base_path->parts};
        $base_path->parts([])->trailing_slash(0);
      

      在这里您可以控制生成的内容。

      【讨论】:

      • 我对此投了反对票,因为您根本没有回答这个问题。您是否建议人们通过在其中添加硬编码前缀来修改 url_for 子代码?因此,每个应用程序实例都必须选择一个经过专门修改的 Mojolicious,无论如何它都会被下一次更新所取代。这是一个糟糕的建议,永远不要那样做。该操作错过了 Apache 配置中的某些内容。
      猜你喜欢
      • 1970-01-01
      • 2019-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-19
      • 2011-05-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多