如何设置代理应用的基本URL?

时间:2015-08-06 20:07:30

标签: perl mojolicious

我需要在Apache反向代理后面放置一个Mojolicious应用程序。我无法让Mojolicious在代理后面生成工作网址。

我在Perl 5.18.1中使用Mojolicious 6.14。

这是我基于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是什么,以便生成正确的链接?

5 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:0)

不知何故一种解决方法,但这就是我解决这个问题的方法:

在配置文件(.conf)中,我定义了base-url:

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

这允许我写这样的模板:

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

答案 2 :(得分:0)

这是一个好问题!从这里和其他地方的一些答案来看,似乎普遍缺乏对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的请求,并将其转发为{{1} }。 /前缀已丢失,这就是为什么基本URL中缺少该前缀,即,所有链接中都缺少该前缀的原因。要修复应用程序生成的url,必须将此前缀添加到基本url中,这将导致我们进入钩子。

挂钩

这是您的钩子函数:

app1

该钩子应该修复基本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,该前缀将附加到所有生成的URL。如果您的模板之一链接到app1,则基本网址应类似于/test,以获取最终网址/app1

这是您的钩子所做的:

  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设置的。实际上,它是由反向代理设置的,而这正是您要寻找的。

  2. /app1/test应该从请求网址中提取前缀。

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

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

  3. 这是将app_one前缀(单个路径令牌)附加到基本URL的位置。基本网址是前缀,用于模板中生成的网址。这就是将/app1变成/test的原因(如果请求是通过反向代理发出的。)

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

修复反向代理

话虽如此,您的反向代理需要操纵请求url以包括前缀以使钩子起作用:

//test

修改后,您的钩子可以正常工作

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

  2. 所有发送到Mojolicious应用程序的请求都将带有ProxyPass /app1 http://localhost:3000/app1 前缀,例如/app1。在此步骤中,将删除前缀以将URL转换为/app1/test

  3. 在第2步中删除的前缀被附加到基本URL,该URL以后将用于生成链接。

这应该解释为什么,您需要在/test行中添加应用程序前缀。没有这种解释,其他人可能会尝试这样做而没有成功,因为他们可能具有不同的挂钩函数。

斜线

一个斜杠会破坏所有内容,并导致大多数请求失败,并显示错误404。

请注意,您的ProxyPass行中的本地目标url(第二个参数)带有斜杠,而path参数则没有。如果不匹配,您可能会在请求网址中以双斜杠结尾,并且某些请求可能会失败。

来自Apache documentation

  

如果第一个参数以结尾/结束,第二个参数也应该以结尾/结束,反之亦然。否则,对后端的最终请求可能会错过一些所需的斜杠,并且无法传递预期的结果。

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

ProxyPass

...生成的url仍将以两个斜杠开头:ProxyPass /app1 http://localhost:3000 = url_for '/test'
这是因为您要将//test附加到要附加应用程序前缀的基本URL。

发生的事情是,在第2步(请参见上文)中,您提取了前缀,并假设应用程序正好在文档根目录下一级运行,即,您的前缀类似于undef而不是{{1 }}(在这种情况下,shift / push例程必须运行两次)。但是ProxyPass指令中没有前缀,因此您的应用程序会看到类似app1的内容,也就是说,没有任何内容可以从apps/app1中提取。而且代码中也没有任何保障措施,因此您最终将/推入了基本URL的parts数组。如果您随后生成一个URL,则Mojolicious会为该parts元素添加一个额外的斜杠,这就是为什么您得到undef的原因。零件数组如下所示:

undef

要解决此双斜杠错误,您可以为钩子添加保护措施:

//test

当然,只要您的反向代理配置中带有前缀,就应始终定义"parts" => [ undef, "test" ],

可以肯定地说,这种方法是一种hack,因为它可以操纵URL。黑客在某些情况下往往会失败。在这种情况下,如果您在直接访问应用程序服务器时手动设置my $path = shift @{$self->req->url->path->parts}; if ($path) { # safeguard push @{$self->req->url->base->path->parts}, $path; } ,则会失败。我在my question中提到过。但是作为开发人员,您可能是唯一可以直接访问该应用程序服务器的人,因为在典型的生产环境中,防火墙只允许外部请求到反向代理。我会留在那里。

答案 3 :(得分:-1)

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

答案 4 :(得分:-1)

当您将应用程序安装到某个位置(而不是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);

您可以在此处控制生成的内容。