检查催化剂

Catalyst 的一个便利之处是它允许您将控制器方法注册为操作,因此不需要单独的 URI 到控制器方法的路由表。然而,这也意味着当 Web 应用程序足够大时,跟踪应用程序响应的所有不同路径可能会变得很棘手。

Catalyst 的链式调度促进了代码重用,但通过混淆路由匹配加剧了问题。Catalyst 还允许声明匹配前缀后无限数量的路径的控制器方法;这是不可预测性的另一个原因。

看看 MetaCPAN 的源代码。你能说出它响应的所有路由吗?

据我所知,唯一的方法是以调试模式启动应用程序,Catalyst 将打印出所有控制器动作及其 URI 列表。我想你可以尝试解析这个输出,但这感觉像是一种黑客行为。如果我们正在编写 Web 应用程序,我们当然应该能够以编程方式检索我们所创建的所有路由,而不必启动应用程序。

Catalyst::Plugin::ActionPaths

在充分构建了这个稻草人之后,让我介绍一下 Catalyst::Plugin::ActionPaths。我以前写过它,为了实施一些工作中的应用公理测试,这些测试检查了 Catalyst 路由的配置是否错误。

该插件向 Catalyst 上下文对象添加了 get_action_paths 方法。该方法返回一个应用程序的 Catalyst::Action 对象数组引用。Catalyst 的路由方式是循环遍历每个动作对象,直到找到与请求匹配的对象,或者返回失败。

要在 MetaCPAN 应用程序上使用 ActionPaths 插件,我 fork 并克隆了 repo,并将 ActionPaths 插件添加到应用程序 中。

从根项目目录安装 Carton 后,我运行了

$ carton install

这将在 ./local 目录中安装应用程序的依赖项。这是一种避免使用应用程序的依赖项破坏系统或用户安装的模块的好方法。

我还必须安装 libxml2-dev 和 node-less Ubuntu 软件包,以提供应用程序的所有依赖项。

最后,我编写了这个 脚本

#!/usr/bin/env perl
use v5.16;
use Cwd;
use File::Basename;
use File::Spec;

my $root_dir;
BEGIN {
  my $bin_dir = File::Basename::dirname(__FILE__);
  $root_dir = Cwd::abs_path(File::Spec->catdir($bin_dir, File::Spec->updir));
}
use lib "$root_dir/local/lib/perl5"; # carton installed deps
use lib "$root_dir/lib";             # root application dir
use Catalyst::Test 'MetaCPAN::Web';

my($res, $c) = ctx_request('/');

for (@{$c->get_action_paths}) {
  say join "\t", $_->{class}, $_->{name}, $_->{path};
}

它首先确定根应用程序目录,并将本地 Carton 安装的路径以及 MetaCPAN 项目模块的路径添加进去(它使用 lib 来捕获特定于架构的嵌套路径)。

它使用 Catalyst::Test 加载 MetaCPAN::Web 应用程序。Catalyst::Test 导出 ctx_request 方法,它返回 Catalyst 上下文对象 $c。从那里我可以调用 get_action_paths 并打印出 MetaCPAN 服务的所有路由。

$ bin/dump-catalyst-paths
MetaCPAN::Web::Controller::Root   index           /
MetaCPAN::Web::Controller::Root   default         /...
MetaCPAN::Web::Controller::About  about           /about/
MetaCPAN::Web::Controller::About  contact         /about/contact/
MetaCPAN::Web::Controller::About  contributors    /about/contributors/
MetaCPAN::Web::Controller::About  development     /about/development/
MetaCPAN::Web::Controller::About  faq             /about/faq/
MetaCPAN::Web::Controller::About  meta_hack       /about/meta_hack/
MetaCPAN::Web::Controller::About  metadata        /about/metadata/
MetaCPAN::Web::Controller::About  missing_modules /about/missing_modules/
MetaCPAN::Web::Controller::About  resources       /about/resources/
MetaCPAN::Web::Controller::About  sponsors        /about/sponsors/
MetaCPAN::Web::Controller::About  stats           /about/stats/
MetaCPAN::Web::Controller::Author index           /author/*
# output truncated

路径中的星号是一个占位符。省略号表示该路径接受无限(!)个占位符。

在这种情况下,我只是打印出控制器方法和它们匹配的 URI,但你可以对 Catalyst::Action 对象实施所有类型的检查,以检测违反协议的开发最佳实践。

更好的方法

上述解决方案可行,但感觉有点不自然。我向Catalyst应用程序中添加了一个插件,而该应用程序实际上并没有使用它。脚本模拟了对应用程序的请求,只是为了获取$c。我正在使用测试模块,但并未运行测试。肯定有更好的方法。

通常,Catalyst应用程序会在应用程序模块中调用setup方法(对于MetaCPAN而言,是MetaCPAN::Web)。setup方法初始化Web应用程序,例如配置目录、初始化日志记录器、加载插件和构建请求分发器。这些存储在应用程序包中,该包是一个单例。

Catalyst::Plugin::ActionPaths::get_action_paths使用Catalyst上下文获取分发器对象,这正是它从Catalyst应用程序中提取路径所需的所有内容。因此,我无需使用request_ctx获取上下文以获取分发器,我只需自行启动应用程序,即可从应用程序包中提取分发器

require MetaCPAN::Web; # calls setup()
my $dispatcher = MetaCPAN::Web->dispatcher;

现在,如果我将get_action_paths方法重写为仅直接使用分发器对象,我就可以在不使用request_ctx的情况下从应用程序中提取所有路径

my $actions = get_action_paths($dispatcher);
for (@{$actions}) {
  say join "\t", $_->{class}, $_->{name}, $_->{path};
}

这可行。当然,如果我可以动态加载MetaCPAN应用程序并提取其路由,那么我也可以为任何Catalyst应用程序执行此操作。这就是dump-catalyst-paths所做的工作。要转储Catalyst应用程序的路由,只需提供包名和任何要包含的附加路径

$ ./dump-catalyst-routes MetaCPAN::Web lib local/lib/perl5

后记

距《Catalyst: The Definitive Guide》出版10周年仅过去了几个月。Catalyst: The Definitive Guide。我们工作中两个核心应用程序都是Catalyst应用程序。作为Perl顶级MVC应用程序之一,它的弹性令人印象深刻。这是对其实施(做了很多事情)以及最近维护者John Napiorkowski所做工作的证明。感谢John!

如果您正在考虑使用Perl进行Web应用程序编程,那么Dancer2MojoliciousKelp框架是Catalyst的现代替代品。

标签

David Farrell

David是一位专业程序员,他经常推文博客有关代码和编程艺术。

查看他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开一个问题或拉取请求来帮助我们。