I very much like the way IO::All allows you to do all kinds of IO operations under a very simple and general interface without having to worry about the details. I was wondering if it could be extended for handling various kinds of URIs; this is what I came up with as a first hack:

Note: I sent this to Brian Ingerson in case he wants to include something like it in a future version of IO::All.

Examples:

# It is trivial to fetch a file by http and save it to an ftp # server, or send it as an email: io("http://a.com") > io("ftp://b.com/incoming/myfile"); io("http://a.com") > io("ftp://me:secret@b.com/myfile"); io("ftp://a.com/pub/xyz") > io("mailto:me@c.com"); # You can also use local files, of course: io("file.txt") > io("mailto:me@c.com?subject=file.txt"); # Or to use the IO object as a filehandle: my $io = io("ftp://b.com/incoming/xyz")->tie; print $io "hello world\n";

Update: Moved the examples before the readmore tag. Brian is very interested in including all sorts of protocols in future versions of IO::All

#!/usr/bin/perl # This script is an example of how IO::All can be extended for handlin +g # various kinds of URIs. Currently it works for http:, ftp:, file: (GE +T only), # and mailto: ("PUT" only) use strict; use warnings; IO::All::URI->import; die "please give two URIs, filenames, or somethings\n" unless (@ARGV > += 2); io(shift) > io(shift); ################################# package IO::All::URI; use IO::All '-Base'; my $ua; # The LWP user agent. It is created the first time it's used sub new { my $new = super; my $name = shift; return $new->lwp($name) if $name =~ /^(https?|ftp|file):/; return $new->mailer($name) if $name =~ /^mailto:/; $new; } sub lwp { my $name = shift; $self->name($name); $self; } # I use Mail::Mailer instead of POSTing to a mailto: URI with LWP beca +use # this seemed simpler and I already had it. However, the other option +still # exists and it would introduce fewer dependencies. sub mailer { require Mail::Mailer; require URI; my $name = shift; $self->name($name); $self->is_open(1); # Uppercase first letter in header names my $i = 1; my %headers = map { $_ = ucfirst if $i++ % 2; $_ } URI->new($name)->headers; # create mailer my $mailer = Mail::Mailer->new->open(\%headers); $self->io_handle($mailer); $self; } sub open { # this regex check wouldn't be duplicated if I added an lwp IO typ +e # instead of using 'file'! Bad form of laziness... if ($self->name =~ /^(https?|ftp|file):/) { $self->open_lwp(@_); } else { super; } } # note that this method only handles the < and > modes. sub open_lwp { # create user agent if it hasn't been created already require LWP::UserAgent; $ua ||= LWP::UserAgent->new; $self->is_open(1); # find out the file mode my ($mode) = @_; $self->mode($mode) if defined $mode; $self->mode('<') unless defined $self->mode; $mode = $self->mode; my $name = $self->name; my $fh; my $content; if ($mode eq '<') { # GET my $content = $ua->get($name)->content or die "couldn't get $n +ame\n"; # create filehandle CORE::open($fh, "<", \$content); $self->io_handle($fh); } elsif ($mode eq '>') { # PUT CORE::open($fh, "+>", \$content); $self->io_handle($fh); } else { die "invalid mode '$mode' for lwp open\n"; } $self; } sub close { if ($self->name =~ /^(https?|ftp|file):/) { $self->close_lwp(@_); } else { super; } } # This is the method that actually sends PUT requests, because we wait + until # we have the whole content before sending it. sub close_lwp { if ($self->mode eq '>') { # PUT $self->is_open(0); my $name = $self->name; # get the content back. This is probably a roundabout way of d +oing it, # but I wasn't sure I wanted to add another field to the IO::A +LL # object... my $fh = $self->io_handle; seek $fh, 0, 0; my $content = do { local $/; <$fh> }; # LWP request my $req = HTTP::Request->new(PUT => $name, undef, $content); my $res = $ua->request($req); unless ($res->is_success) { die "PUT error: " . $res->status_line . "\n"; } } $self; } # This is not a very efficient way of copying large files, but it's a +quick # hack while I add 'uri' as a type in the overload_table. This should +work # for files and uris (including mailer). sub overload_file_to_file() { my $temp; $_[1] > $temp; $temp > $_[2]; } # same here sub overload_file_from_file() { my $temp; $_[2] > $temp; $temp > $_[1]; } # This is to ensure that PUT requests are done automatically. Another +option # would be to create an intermediate filehandle object for the URI, wh +ich would # be destroyed when the IO::All object falls out of scope. This seemed + simpler. sub DESTROY { $self->close; super; }

Replies are listed 'Best First'.
Re: IO::All::URI
by Anonymous Monk on Jun 07, 2004 at 07:18 UTC

    I really love (good code) recycling. And i have the strong feeling in my spine that curl (via WWW::Curl::easy would be the ideal candidate to be responsible in the backend for IO::All.

    I would prefer it ower at HTTP::Request/LWP approach for various reasons - that may be discussed here. Even when WWW::Curl::easy would need additional volunteering/maturation.

    Cheers, muenalan@cpan.org
      LWP::UserAgent is pure perl, so it should be preferred over WWW::Curl. But maybe this could be made as a configuration option (e.g. IO::All::URI->use_backend("WWW::Curl")).

      Could you expand on why WWW::Curl::easy would be a better option? I looked at it and couldn't figure out how to use it, but that's surely because I'm not familiar with libcurl. As esserte says, it could be included as an option, but I really prefer LWP as the default, not only because it is pure Perl, but also because it is the standard that many people have already installed.