FastCGI、Perl 和退出

FastCGI, Perl and Exit

在过去的几周里,我一直在磨练我用于项目的大型、数十年历史的代码库的性能,这里有人建议我应该看看 FastCGI 或 HTTP::Engine 之类的东西。我发现使用 FastCGI 非常简单,但有一个烦人的问题我找到了不同的答案。

我读过的一些文档说你永远不应该通过 FastCGI 在 运行 的脚本上调用 exit,因为这会损害保持它持久加载的整个概念。其他人说没关系。我的代码在很多地方都使用了 exit,在这些地方确保没有任何东西继续执行很重要。例如,我有调用授权检查的受限访问组件:

use MyCode::Authorization;
our $authorization = MyCode::Authorization->new();

sub administration {
    $authorization->checkCredentials();

    #...Do restricted access stuff.
}

为了尽可能避免代码中出现错误,从而允许某些人在不应该访问这些函数的情况下被允许访问这些函数,checkCredentials 在生成后使用 exit() 结束进程带有登录页面的用户友好响应 if 答案是用户没有适当的凭据。例如:

sub checkCredentials {
   #Logic to check credentials

   if ($validCredential) {
       return 1;
   }
   else {
       # Build web response.
       # Then:
       exit;
   }
}

}

我使用它是为了防止我不小心忽略了继续进行的导致安全漏洞的事情。目前,如果提供了正确的凭据,调用例程可以安全地假设它只会从 checkCredentials 取回控制权。

但是,我想知道是否需要删除这些调用以充分利用 FastCGI。 FCGI 的 $req->Finish()(或 PSGI 中 HTTP::Engine 的等价物)是否足够替代?

I've read say you should never call exit on a script being run through FastCGI,

您不希望进程退出,因为使用 FastCGI 的目的是使用单个进程处理多个请求(以避免加载时间等)。

所以你想做的是覆盖退出,这样就结束了你的请求特定代码,而不是 FastCGI 请求循环。


您可以覆盖 exit,但您必须在编译时这样做。所以使用一个标志来表示覆盖是否处于活动状态。

our $override_exit = 0;
BEGIN { 
   *CORE::GLOBAL::exit = sub(;$) {
      die "EXIT_OVERRIDE\n" if $override_exit;
      CORE::exit($_[0] // 0);
   };
}

while (get_request()) {
   # Other setup...

   eval {
      local $override_exit = 1;
      handle_request();
   };
    
   my $exit_was_called = $@ eq "EXIT_OVERRIDE\n";
   log_error($@) if $@ && !$exit_was_called;
    
   log_error("Exit called\n") if $exit_was_called;

   # Other cleanup...
}

但这会产生一个可能会被无意中捕获的异常。因此,让我们改用 last

our $override_exit = 0;
BEGIN { 
   *CORE::GLOBAL::exit = sub(;$) {
      no warnings qw( exiting );
      last EXIT_OVERRIDE if $override_exit;
      CORE::exit($_[0] // 0);
   };
}

while (get_request()) {
   # Other setup...

   my $exit_was_called = 1;
   EXIT_OVERRIDE: {
      local $override_exit = 1;
      eval { handle_request() };
      log_error($@) if $@;
      $exit_was_called = 0;
   }

   log_error("Exit called\n") if $exit_was_called;

   # Other cleanup...
}