POE::Filter(3pm)

πŸ’– POE::Filter πŸ’–

protocol abstractions for POE::Wheel and standalone use β€” bestie, this is the Perl manual page for POE::Filter, straight outta the User Contributed Perl Documentation. Let's get into it ✨


🌸 SYNOPSIS 🌸

So you can use these two ways. First way: with POE::Wheel classes, you just hand a POE::Filter object to one of the ...Filter constructor parameters, like:

#!perl

use POE qw(Filter::Line Wheel::FollowTail);

POE::Session->create(
  inline_states => {
    _start => sub {
      $_[HEAP]{tailor} = POE::Wheel::FollowTail->new(
        Filename => "/var/log/system.log",
        InputEvent => "got_log_line",
        Filter => POE::Filter::Line->new(),
      );
    },
    got_log_line => sub {
      print "Log: $_[ARG0]\n";
    }
  }
);

POE::Kernel->run();
exit;

And second way β€” standalone, no POE needed, no cap:

#!perl

use warnings;
use strict;
use POE::Filter::Line;

my $filter = POE::Filter::Line->new( Literal => "\n" );

# Prints three lines: one, two three.

$filter->get_one_start(["one\ntwo\nthr", "ee\nfour"]);
while (1) {
  my $line = $filter->get_one();
  last unless @$line;
  print $line->[0], "\n";
}

# Prints two lines: four, five.

$filter->get_one_start(["\nfive\n"]);
while (1) {
  my $line = $filter->get_one();
  last unless @$line;
  print $line->[0], "\n";
}

πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so POE::Filter objects plug into the wheels and define how the data gets serialized for writing and parsed after reading. The POE::Wheel objects are the ones actually moving the data around β€” the POE::Filter objects just decide what that data should look like. It's giving division of labor, it's giving teamwork πŸ’…

POE::Filter objects are simple by design. They don't use POE internally, so they're limited to serialization and parsing. That can make certain protocols (like HTTP 1.x) trickier to implement, but the tradeoff is that filters can be used in stand-alone programs. Worth it, honestly.

Stand-alone use is very important, bestie. It lets application developers create lightweight blocking libraries that can act as simple clients for POE servers. POE::Component::IKC::ClientLite is a notable example. This lightweight, blocking event-passing client supports thin clients for gridded POE applications. The canonical use case is injecting events into an IKC application or grid from CGI interfaces, which require lightweight resource use. Iconic behavior fr fr.

POE filters and drivers pass data in array references. Slightly awkward, yes, but it minimizes the amount of data that has to be copied on Perl's stack. Lowkey a genius move.


πŸ‘‘ PUBLIC INTERFACE πŸ‘‘

Every single POE::Filter class has to support the minimal interface defined right here. Specific filters may implement and document extra methods on top of that. No skipping the basics πŸ’–

✨ new PARAMETERS

new() creates and initializes a new filter. Constructor parameters vary from one POE::Filter subclass to the next, so please go read the documentation for your desired filter. Every filter's got its own vibe, iykyk.

πŸŽ€ clone

clone() creates and initializes a new filter based on the constructor parameters of the existing one. The new filter is a near-identical copy β€” except its buffers are empty. Fresh start energy.

Certain components, like POE::Component::Server::TCP, use clone(). These components accept a master or template filter at creation time, then clone() that filter for each new connection.

my $new_filter = $old_filter->clone();

🌈 get_one_start ARRAYREF

get_one_start() accepts an array reference containing unprocessed stream chunks. Those chunks get added to the filter's internal buffer for parsing by get_one().

The SYNOPSIS shows get_one_start() in use. Go look, bestie πŸ‘€

⭐ get_one

get_one() parses zero or one complete item from the filter's internal buffer. The data comes back as an ARRAYREF, suitable for passing to another filter or a POE::Wheel object. Filters will return empty ARRAYREFs if they don't have enough raw data to build a complete item. No data, no item, periodt.

get_one() is the lazy form of get(). It only parses one item at a time from the filter's buffer. This is vital for applications that may switch filters in mid-stream, because it ensures the right filter is in use at any given time. Main character energy for the lazy method honestly.

The SYNOPSIS shows get_one() in use. Note how it assumes the return is always an ARRAYREF, and it implicitly handles empty ones. So clean, so elegant.

πŸ’œ get ARRAYREF

get() is the greedy form of get_one(). It accepts an array reference containing unprocessed stream chunks, adds that data to the filter's internal buffer, then parses as many full items as possible from the buffer and returns them in another array reference. Any unprocessed data stays in the filter's buffer for the next call.

Same as get_one() β€” get() returns an empty array reference if the filter doesn't contain enough raw data to build a complete item.

Fun fact: get() is actually implemented in POE::Filter in terms of get_one_start() and get_one().

Here's the get() form of the SYNOPSIS stand-alone example:

#!perl

use warnings;
use strict;
use POE::Filter::Line;

my $filter = POE::Filter::Line->new( Literal => "\n" );

# Prints three lines: one, two three.

my $lines = $filter->get(["one\ntwo\nthr", "ee\nfour"]);
foreach my $line (@$lines) {
  print "$line\n";
}

# Prints two lines: four, five.

$lines = $filter->get(["\nfive\n"]);
foreach my $line (@$lines) {
  print "$line\n";
}
⚠️ get() should not be used with wheels that support filter switching. Its greedy nature means it often parses streams well in advance of a wheel's events. By the time an application changes the wheel's filter, it's too late β€” the old filter has already parsed the rest of the received data. Devastating. πŸ’€

Consider a stream of letters, numbers, and periods. The periods signal when to switch filters from one that parses letters to one that parses numbers.

In our hypothetical application, letters must be handled one at a time, but numbers may be handled in chunks. We'll use POE::Filter::Block with a BlockSize of 1 to parse letters, and POE::FIlter::Line with a Literal terminator of "." to handle numbers.

Here's the sample stream:

abcdefg.1234567.hijklmnop.890.q

We'll start with a ReadWrite wheel configured to parse characters.

$_[HEAP]{wheel} = POE::Wheel::ReadWrite->new(
  Filter => POE::Filter::Block->new( BlockSize => 1 ),
  Handle => $socket,
  InputEvent => "got_letter",
);

The "got_letter" handler will be called 8 times. One for each letter from a through g, and once for the period following g. Upon receiving the period, it will switch the wheel into number mode.

sub handle_letter {
  my $letter = $_[ARG0];
  if ($letter eq ".") {
    $_[HEAP]{wheel}->set_filter(
      POE::Filter::Line->new( Literal => "." )
    );
    $_[HEAP]{wheel}->event( InputEvent => "got_number" );
  }
  else {
    print "Got letter: $letter\n";
  }
}

If the greedy get() were used, the entire input stream would have been parsed as characters in advance of the first handle_letter() call. The set_filter() call would have been moot, since there'd be no data left to parse. Rude.

The "got_number" handler receives contiguous runs of digits as period-terminated lines. The greedy get() would cause a similar problem as above.

sub handle_numbers {
  my $numbers = $_[ARG0];
  print "Got number(s): $numbers\n";
  $_[HEAP]->{wheel}->set_filter(
    POE::Filter::Block->new( BlockSize => 1 )
  );
  $_[HEAP]->{wheel}->event( InputEvent => "got_letter" );
}

So don't do it! πŸ™ˆ Just don't.

πŸ“ put ARRAYREF

put() serializes items into a stream of octets that may be written to a file or sent across a socket. It accepts a reference to a list of items, and it returns a reference to a list of marshalled stream chunks. Heads up: the number of output chunks is not necessarily related to the number of input items.

In stand-alone use, put()'s output may be sent directly:

my $line_filter = POE::Filter::Line->new();
my $lines = $line_filter->put(\@list_of_things);
foreach my $line (@$lines) {
  print $line;
}

The list reference it returns may be passed directly to a driver or filter. Drivers and filters deliberately share the same put() interface so that things like this are possible:

$driver->put(
  $transfer_encoding_filter->put(
    $content_encoding_filter->put(
      \@items
    )
  )
);

1 while $driver->flush(\*STDOUT);

Nested like a lil matryoshka, obsessed πŸͺ†

🧚 get_pending

get_pending() returns any data remaining in a filter's input buffer. The filter's input buffer is not cleared, though. get_pending() returns a list reference if there's any data, or undef if the filter was empty.

POE::Wheel objects use get_pending() during filter switching. Unprocessed data is fetched from the old filter with get_pending() and injected into the new filter with get_one_start().

use POE::Filter::Line;
use POE::Filter::Stream;

my $line_filter = POE::Filter::Line->new();
$line_filter->get_one_start([ "not a complete line" ]);

my $stream_filter = POE::Filter::Stream->new();
my $line_buffer = $line_filter->get_pending();
$stream_filter->get_one_start($line_buffer) if $line_buffer;

print "Stream: $_\n" foreach (@{ $stream_filter->get_one });

Full items are serialized whole, so there is no corresponding put buffer or accessor. That's just how it is, bestie.


πŸ”₯ SEE ALSO πŸ”₯

The SEE ALSO section in POE contains a table of contents covering the entire POE distribution. Very organized of it.

POE is bundled with the following filters:

  • POE::Filter::Block
  • POE::Filter::Grep
  • POE::Filter::HTTPD
  • POE::Filter::Line
  • POE::Filter::Map
  • POE::Filter::RecordBlock
  • POE::Filter::Reference
  • POE::Filter::Stackable
  • POE::Filter::Stream

A whole squad, honestly 🌈


πŸ’€ BUGS πŸ’€

In theory, filters should be interchangeable. In practice, stream and block protocols tend to be incompatible. It's giving "we tried". 😭


🫢 AUTHORS & COPYRIGHTS 🫢

Please see POE for more information about authors and contributors.


And that's the whole page, bestie! This was perl v5.34.0, dated 2022-03-25 β€” the POE::Filter(3pm) manual page. Thanks for reading with me, ily, go forth and filter responsibly πŸ’–βœ¨πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from