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

StackOverflow https://stackoverflow.com/questions/6011793

  •  14-11-2019
  •  | 
  •  

Question

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.

Was it helpful?

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.

OTHER TIPS

These are not bugs - It's actually easier to call this a bug in Plack and fix it to handle them as a valid response. But that would make things worse, since now Plack handles things that are not (clearly) defined as a proper response in the PSGI specification. (PSGI != Plack, likewise HTTP != Apache)

The point of PSGI specification is that it is a common interface between web servers and applications. If a server/application needs to add extra 2-3 lines of code to conform to the specification, that is a good compromise. It's much better to have 2-3 lines of code in each N applications and M servers, than having N * 2-3 lines of extra code to handle corner cases in servers and vice versa in applications.

The spec defines the response body should be either a "built-in filehandle" or "IO::Handle-like object that implements getline". Handling something that works similar to this is easy in Plack, but we shouldn't blindly do it - remember, Plack is not the only PSGI implementation. The Lint middleware warning you about that incompatibility is a right thing.

That said:

a) IO::Scalar is an object that implements getline() method, so it should be accepted. It's unfortunate that the Lint dying on it because of the bug of the module, as pointed out by others. It could easily be worked around with the monkey patching, and it's also easy to patch Plack::Util::is_real_fh to trap errors in the ->fileno call, but again, I need to think about it if it is a right thing to do.

b) PerlIO in-memory filehandle is a tricky thing. The spec only says "built-in filehandle" and the in-memory filehandle could be argued as a built-in thing as well. Actually, if you disable the Lint middleware (with -E production option for plackup, for example), the filehandle would just work. But again, the Lint middleware gives you a message because it's not guaranteed to work elsewhere.

At last but not least, this should probably be addressed in the FAQ. Feel free to open a case in the psgi-specs repository.

Looks like a bug in IO::Scalar. Report it, and insteaad use IO::String or the built-in in memory file support added in 5.8.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top