[Catalyst] Plack::Middleware::ContentLength problem

neil.lunn neil at mylunn.id.au
Sat Dec 21 04:34:24 GMT 2013

Considering a possible option to remove code from Catalyst that inspects =

the content length of the response body, I was looking the the code for =

Plack::Middleware::ContentLength =

ContentLength.pm>. =

The meat is really in Plack::Util, and as a dependency of =

HTTP::Server::PSGI there is no additional dependency for Catalyst. So it =

could even be a default middleware, retaining (replacing) what Catalyst =

already does.

While the implementation is similar to the Catalyst code there is a bit =

more to it, mostly in Plack::Util::is_real_fh. The function does a =

number of checks to determine whether the filehandle is real before =

handing over to filestat -s
to get the size. There are noted problems with things that may look like =

a filehandle but are not specifically a typical filehandle, so this is =

valid. But there may be cases where you want to do so. My article today =

actually (http://www.catalystframework.org/calendar/2013/21), even =

though I'm actually talking here about the above case.

So I can play with the filehandle implementation class enough to get the =

"is_real_fh" test to return true. But there seems to be a problem in =

implementing the middleware.

 From the test code I get the right result. But note the inclusion order =

of Plack::Util. If that is loaded before the class
that does the required mucking around, the function passes, if not the =

function will fail. This all revolves around overriding the built-in =

"fileno" as this is used in "is_real_fh".

So it seems the same issue is happening with loading order when the =

middleware is applied to a PSGI app. But I'm not sure how or whether =

there is some other funny thing going on.

Any Ideas? Pointers?


Test case code. Also with another version of is_real_fh with extended =


#!/usr/bin/env perl
use Modern::Perl;

use Scalar::Util;
#use Plack::Util;
use MyApp::FunnyIO::Domain::GzipData;
use MyApp::FunnyIO::Domain::FunnyIO;
use Plack::Util;

my $comp =3D MyApp::FunnyIO::Domain::GzipData->new->getData();
my $body =3D MyApp::FunnyIO::Domain::FunnyIO->new( \$comp );

if ( -p $body or -c _ or -b _ ) {
   say "nope";
} else {
   say "yep";

my $reftype =3D Scalar::Util::reftype($body);
if ( $reftype eq 'IO' or $reftype eq 'GLOB' && *{$body}{IO} ) {
   say "yep";
} else {
   say "nope";

my $m_fileno =3D $body->fileno;
say "defined" if defined $m_fileno;
say "->fileno " . $m_fileno;

if ( Plack::Util::is_real_fh( $body ) ) {
   say "yep i am real";
} else {
   say "nope not real";

say ref( $body );
my $f_fileno =3D fileno( $body );
say "defined" if defined $f_fileno;
say "fileno " . $f_fileno;
say -s $body;

my $size =3D Plack::Util::content_length( $body );
say $size;

package MyTest;
use overload();

sub TRUE { 1=3D=3D1 }
sub FALSE { !TRUE }

sub is_real_fh {
   my $fh =3D shift;
     no warnings 'uninitialized';
     if ( -p $fh or -c _ or -b _ ) {
       warn "do not like file test";
       return FALSE;

   my $reftype =3D Scalar::Util::reftype($fh) or return;
   if (     $reftype eq 'IO'
         or $reftype eq  'GLOB' && *{$fh}{IO}
   ) {
       my $m_fileno =3D $fh->fileno;
       unless( defined $m_fileno ) {
         warn "m_fileno not defined";
         return FALSE;

       unless( $m_fileno >=3D 0 ) {
         warn "m_fileno less than 0";
         return FALSE;

       my $f_fileno =3D fileno( $fh );
       unless(  defined $f_fileno ) {
         warn "f_fileno not defined";
         return FALSE;

       unless( $f_fileno >=3D 0 ) {
         warn "f_fileno less than 0";
         return FALSE;

       return TRUE;

   } else {

       warn "Don't like GLOB type";
       return FALSE;


And the code for the Filehandle ('like') class:

package MyApp::FunnyIO::Domain::FunnyIO;
use Moose;
use Moose::Exporter;
use MooseX::NonMoose::InsideOut;
extends 'IO::Uncompress::Gunzip';
use IO::Scalar;
use namespace::sweep;

   *CORE::GLOBAL::fileno =3D sub {
     my $fh =3D shift;
     return 0 if ( ref $fh eq 'MyApp::FunnyIO::Domain::FunnyIO' );
     return CORE::fileno($fh);

use overload
   'bool'  =3D> sub {1},
   '-X'    =3D> \&myFileTest;

   as_is =3D> ['fileno'],

has '_content' =3D> ( is =3D> 'ro' );

sub fileno {
   my $fh =3D shift;
   return fileno($fh);

sub myFileTest {
   my ( $self, $arg ) =3D @_;

   if ( $arg eq "s" ) {

     my $io =3D IO::Scalar->new( $self->_content );
     $io->seek( -4, 2 );
     $io->read(  my $buf, 4 );
     return unpack( 'V', $buf );

   } elsif ( $arg eq "p" ) {
     return undef;
   } else {
     die "Got: $arg Only implementing a size operator at this time";


around BUILDARGS =3D> sub {
   my ( $orig, $class, $ref ) =3D @_;
   return $class->$orig({ '_content' =3D> $ref });

   my ( $class, $args ) =3D @_;
   return $args;

no Moose;

And for a simple PSGI app:

use MyApp::FunnyIO::Domain::GzipData;
use MyApp::FunnyIO::Domain::FunnyIO;
use Plack::Builder;
#use Plack::Util;
#use Plack::Middleware::ContentLength;

builder {

   enable 'Plack::Middleware::ContentLength';

   my $app =3D sub {
     my $env =3D shift;

     my $comp =3D MyApp::FunnyIO::Domain::GzipData->new->getData();
     my $body =3D MyApp::FunnyIO::Domain::FunnyIO->new( \$comp );

     return [
       [ 'Content-Type' =3D> 'text/plain' ],



This email is free from viruses and malware because avast! Antivirus protec=
tion is active.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20131221/55c42=

More information about the Catalyst mailing list