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 <http://cpansearch.perl.org/src/MIYAGAWA/Plack-1.0030/lib/Plack/Middleware/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?

Neil

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

#!/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 = MyApp::FunnyIO::Domain::GzipData->new->getData();
my $body = MyApp::FunnyIO::Domain::FunnyIO->new( \$comp );

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

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

my $m_fileno = $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 = fileno( $body );
say "defined" if defined $f_fileno;
say "fileno " . $f_fileno;
say -s $body;

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

package MyTest;
use overload();

sub TRUE { 1==1 }
sub FALSE { !TRUE }

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

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

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

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

      unless( $f_fileno >= 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;

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

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

Moose::Exporter->setup_import_methods(
  as_is => ['fileno'],
);

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

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

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

  if ( $arg eq "s" ) {

    my $io = 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 => sub {
  my ( $orig, $class, $ref ) = @_;
  return $class->$orig({ '_content' => $ref });
};


sub FOREIGNBUILDARGS {
  my ( $class, $args ) = @_;
  return $args;
}

no Moose;
__PACKAGE__->meta->make_immutable;
1;

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 = sub {
    my $env = shift;

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

    return [
      '200',
      [ 'Content-Type' => 'text/plain' ],
      $body,
    ];

  };

}









---
This email is free from viruses and malware because avast! Antivirus protection 
is active.
http://www.avast.com
_______________________________________________
List: [email protected]
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/[email protected]/
Dev site: http://dev.catalyst.perl.org/

Reply via email to