【问题标题】:PSGI Response: What kinds of filehandles can be expected to work with PSGI, and Plack?PSGI 回应:哪些类型的文件句柄可以与 PSGI 和 Plack 一起使用?
【发布时间】:2011-05-15 22:54:54
【问题描述】:

PSGI specification 将 HTTP 响应定义为由三部分组成,其中第三部分可以是数组引用或文件句柄。文件句柄可能是:

IO::Handle 类对象或内置文件句柄。

规范继续说:

服务器可以使用 fileno 和 Scalar::Util::reftype 检查主体是否是真实的文件句柄,如果它是具有文件描述符的真实文件句柄,它可以使用 sendfile(2) 等技术优化文件服务。

现在我使用plackup(Plack 版本 0.9978)拼凑了一个命令行示例,似乎检查正文是否是真正的文件句柄会导致致命错误:

Can't locate object method "FILENO" via package "IO::Scalar" at /usr/lib/perl5/5.10/i686-cygwin/IO/Handle.pm line 390

这是命令行示例:

plackup -MData::Dumper -MIO::Scalar -e \
'sub { $env=shift; return [200, [], IO::Scalar->new(\Dumper $env) ] }'

当然我不能使用文件句柄:

plackup --port 9999 -MData::Dumper -e \
'sub { $env=shift; return [200, [], [Dumper $env] ] }'

但我感兴趣的是什么有效,什么无效。那么,Plack 在句柄上调用FILENO 时不应该更加谨慎,以免遇到异常吗?

再添加一个:

plackup --port 9999 -MData::Dumper -e \
'sub{$env=shift; $s=Dumper $env; open $fh,q(<),\$s or die; return [200,[],$fh ]}'

看起来文件句柄没有被识别。错误信息是:

body should be an array ref or filehandle at /usr/lib/perl5/site_perl/5.10/Plack/Middleware/StackTrace.pm line 35

更新:

正如他在回答中所说的那样,以下内容将起作用(至少在 Cygwin 上的 5.10.1 上):

plackup -p 9999 -MData::Dumper -MIO::String -e \
'sub { return [200, [], IO::String->new(\Dumper shift) ] }'

但很明显,从失败的示例中可以看出某个地方存在问题,一旦我确定它实际上是什么,就会报告它。

【问题讨论】:

    标签: perl plack psgi


    【解决方案1】:

    这些不是错误 - 实际上更容易在 Plack 中将此称为错误并修复它以将它们作为有效响应进行处理。但这会让事情变得更糟,因为现在 Plack 处理的事情没有(明确)定义为 PSGI 规范中的正确响应。 (PSGI != Plack,同样 HTTP != Apache)

    PSGI 规范的重点在于它是 Web 服务器和应用程序之间的通用接口。如果服务器/应用程序需要添加额外的 2-3 行代码以符合规范,这是一个很好的折衷方案。在每 N 个应用程序和 M 个服务器中使用 2-3 行代码比在服务器中处理极端情况以及在应用程序中使用 N * 2-3 行额外代码要好得多。

    规范定义响应主体应该是“内置文件句柄”或“IO::Handle-like object that implements getline”。在 Plack 中处理与此类似的事情很容易,但我们不应该盲目地这样做——记住,Plack 不是唯一的 PSGI 实现。 Lint 中间件警告您不兼容是一件正确的事情。

    也就是说:

    a) IO::Scalar 是一个实现 getline() 方法的对象,所以它应该被接受。正如其他人所指出的那样,由于模块的错误,Lint 死在上面是很不幸的。它可以很容易地通过猴子修补来解决,并且也很容易修补 Plack::Util::is_real_fh 以在 ->fileno 调用中捕获错误,但同样,我需要考虑它是否是 正确的事情

    b) PerlIO 内存中的文件句柄是一件棘手的事情。规范只说“内置文件句柄”,内存中的文件句柄也可以被认为是内置的东西。实际上,如果您禁用 Lint 中间件(例如,对于 plackup 使用 -E production 选项),文件句柄就可以正常工作。但同样,Lint 中间件会给你一个消息,因为它不能保证在其他地方工作。

    最后但同样重要的是,这可能应该在常见问题解答中得到解决。随时在psgi-specs 存储库中打开一个案例。

    【讨论】:

    • 从规范中我似乎很清楚,内存中的文件句柄是可以的。他们实现getlineclose 并从fileno 返回false 以防止sendfile 优化。如果一个实现getlineclose 的对象可以做任何事情,那么它似乎内存中的文件句柄也应该是可以的。在哪些情况下它们不起作用?
    • 应该没问题,只要服务器不检查响应类型来确定要做什么 - 至少我们有 ARRAY ref 和一个对象或文件句柄,所以简单的实现是if (ref $res-&gt;[2] eq 'ARRAY') { respond_from_array() } else { # do something with $res-&gt;[2]-&gt;getline }。实际上,这就是 PSGI 文档所建议的 - 所以你说得对,它应该主要工作。对于 Lint 来说,确定文件句柄是否在没有特殊大小写 GLOB ref 的情况下实现 getline() 有点棘手。不过,它可能不得不这样做。
    • open my $io, "&lt;", \"Hello"; $io-&gt;can("getline"); # false / $io-&gt;getline() # actually works 这就是为什么它很棘手。有关详细信息,请参阅Yuval's post。 (这就是为什么祝福 IO::Handle 是一种解决方法)
    • @miyagawa,感谢您的回答以及您在 PSGI/Plack 开发方面所做的工作。我知道 PSGI != Plack 以及什么是规范以及什么是实现,但在我看来,规范也清楚地表明了普通的旧文件句柄是可以的。如果您规定 $io-&gt;can("getline") 应评估为 true,那么似乎必须针对规范提出该错误,该规范应明确说明实现必须满足的要求。
    • 我建议修改 validate_res 中的检查以添加额外的 ||条件:(ref $res-&gt;[2] eq 'GLOB' &amp;&amp; *{$res-&gt;[2]}{IO} &amp;&amp; *{$res-&gt;[2]}{IO}-&gt;can('getline'))
    【解决方案2】:

    这似乎是 Plack 中的一个错误。它试图通过 fileno 确定它是否有一个真正的文件句柄,如果没有,它将只接受带有 getline 方法的对象。这错过了没有定义FILENO 的绑定文件句柄(如果不礼貌,则有效)和内存中的文件句柄,这些文件句柄没有有效的文件号,也不是受祝福的对象。你可以在Plack::Middleware::Lint-&gt;validate_resPlack::Util-&gt;is_real_fh的逻辑中看到。

    我会report it to Plack as a bug

    同时,您可以通过将 IO::Scalar::FILENO 定义为返回 undef 来解决 IO::Scalar 中的问题。

    sub IO::Scalar::FILENO { return }
    

    这将是对 IO::Scalar 的改进,但它已经六年没有更新了,所以我不会屏住呼吸。

    要允许内存中的文件句柄,您可以通过祝福文件句柄来欺骗 Plack。在打开它和把它交给它之间的某个时候,这样做:

    bless $fh, "IO::Handle";
    

    这是无害的,因为任何文件句柄都会响应 IO::Handle 方法。但也请务必将其报告为错误。

    【讨论】:

    • 正常内存文件句柄没有问题(如果你使用过 IO::Handle);它只是 IO::Scalar 并没有兑现其像 IO::Handle 一样的承诺。
    • @ysth,你能澄清一下“内存文件句柄没有问题”吗?在我的示例中,无论有无 IO::Handleplackup -MData::Dumper -MIO::Handle -e 'sub { open $fh, "&lt;", \Dumper shift; return [200, [], $fh ] }'(Cygwin 上的 Perl 5.10.1),他们显然都这样做了
    • @Schwern,谢谢。一旦我清楚它们到底是什么,我会报告任何错误。 (目前,我会说他们在 Plack 中,当谈到以下声明时,它似乎无法辜负 PSGI:“一个 IO::Handle-like 对象或内置文件句柄。”)
    • 哦,对不起,他们调用fileno没有问题,但是没有通过后面的bless和can->('getline')检查;你建议的祝福适用于此。
    • 与使用 ioref 代替 globref 一样:open $fh, "&lt;", \$buffer; $fh=*$fh{IO}$fh = Symbol::geniosym; open $fh, "&lt;", \$buffer
    【解决方案3】:

    看起来像 IO::Scalar 中的错误。报告它,并使用 IO::String 或 5.8 中添加的内置内存文件支持。

    【讨论】:

    • 谢谢。我更新了我的问题以反映 IO::String 确实有效的事实。您能否阐明您如何看待“内置内存文件支持”的工作方式?我假设你的意思是打开一个文件句柄到一个在内存中保存一个字符串的标量。无论IO::Handle 是否已加载,这在我在问题中给出的示例中都失败了。至少在 Cygwin 上的 Perl 5.10.1 中。
    • 我只是测试了 fileno,而不是 plack 正在做的其他事情;在别处见我的 cmets。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-21
    • 2013-02-26
    • 2012-09-21
    • 1970-01-01
    相关资源
    最近更新 更多