Search code examples
perlplackpsgi

PSGI Response: What kinds of filehandles can be expected to work with PSGI, and Plack?


The PSGI specification defines the HTTP response as consisting of three parts, the third of which may be either an array reference or a filehandle. The filehandle may be:

An IO::Handle-like object or a built-in filehandle.

And the spec goes on to say:

Servers MAY check if the body is a real filehandle using fileno and Scalar::Util::reftype and if it's a real filehandle that has a file descriptor, it MAY optimize the file serving using techniques like sendfile(2).

Now I've cobbled together a command-line example using plackup (Plack version 0.9978), and it appears that checking if the body is a real filehandle results in a fatal error:

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

Here's the command-line example:

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

Of course I could just not use a filehandle:

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

But I'm interested in what works and what doesn't. So shouldn't Plack exercise more caution when calling FILENO on the handle so it wouldn't run into an exception?

And to add another one:

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

Looks like the filehandle isn't recognized as such. The error message is:

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

Update:

As ysth stated in his answer, the following will work (at least on 5.10.1 on Cygwin):

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

But clearly, there is an issue someplace as can be seen from the failing examples, and it will be reported once I've made up my mind what it actually is.


Solution

  • This appears to be a bug in Plack. It tries to figure out if it has a real filehandle, via fileno, and if not it will only accept objects with a getline method. This misses out on both tied filehandles without FILENO defined (valid, if impolite) and in memory filehandles which do not have a valid fileno nor are they blessed objects. You can see it in the logic in Plack::Middleware::Lint->validate_res and Plack::Util->is_real_fh.

    I'd report it to Plack as a bug.

    Meanwhile, you can work around the problem in IO::Scalar by defining IO::Scalar::FILENO to return undef.

    sub IO::Scalar::FILENO { return }
    

    This would be an improvement to IO::Scalar, but it hasn't been updated in six years so I wouldn't hold my breath.

    To allow in memory filehandles, you can trick Plack by blessing the filehandle. Sometime between opening it and handing it off, do this:

    bless $fh, "IO::Handle";
    

    That's harmless as any filehandle will respond to IO::Handle methods anyway. But also please do report it as a bug.