Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask

Extending MIME::Lite

by demerphq (Chancellor)
on Sep 27, 2003 at 11:44 UTC ( #294634=perlmeditation: print w/replies, xml ) Need Help??

Recently trs80 contacted me with a patch for MIME::Lite so that it would handle SMTP servers that require authentication in an integrated way. His initial patch (which he himself called not-production-ready) involved adding two properties to the internal object (auth_user and auth_pwd), two methods for manipulating these properties and the required code to begin the SMTP transaction with the required autheticaion call to the underlying SMTP object and server. My response was positive. Adding this can only make MIME::Lite more powerful and useful and as such is A Very Good Thing.

The problem for me is that I do not want to add methods and properties for this. MIME::Lite is a generic tool for generating MIME encoded emails. It need not send via SMTP, as it currently supports at least three transport mechanisms, and in fact is often not even used for the actual transport at all. Furthermore there is already a defined mechanism for setting transport options at a class level so that any MIME::Lite objects created will use the preconfigured options. The proposed changes don't play well with that angle as they require properties to be stored in the object itself, which to an extent precludes the existings transport configuration framework.

My next problem is that I am not the hugest fan of the transport configuration as it is. :-) Were I to be writing the send_by_smtp option brand new, I would not make it pass its arguments directly through to Net::SMTP, instead I would create my own arguments (closely modeled on Net::SMTP) so that this was more flexiblity for extension. But alas, unless I want to rewrite the entire module as a backwards compatibilty interface to a new cleaner interface (which actually isnt that bad an idea, beyond my having insufficient time to do it), I am constrained in what I can do.

So what im thinking is to create an extensible interface for handling things like this in a generic way. I proposed to trs80 that instead of creating two new methods we simply provide for a means to use the current configuration interface to do the same. My thought for doing this was to allow the send() method to take a hashref of "special options". These special options would easily be detectable in send_by_XXXX and either ignored or acted upon as was relevent.

To me this would provide an extensibilty pathway for transport options going forward. It would also alow me to provide a cleaner way of specifying options to Net::SMTP (I want to be able to turn SMTP debugging on without having to know what SMTP server I am talking to, and without having to pass undef as the server name, mostly because I think its confusing for beginners and also because that undef looks ugly.)

So the rub of the matter is what issues do you see with having to say

MIME::Lite->send('smtp',{auth_user=>'foo',auth_pwd=>'bar'}, $server,@Net_SMTP_opts); $MIME_Lite->send_by_smtp({auth_user=>'foo',auth_pwd=>'bar'}, $server,@Net_SMTP_opts); $MIME_Lite->send_by_smtp({Debug=>1});

Would this be confusing? Do you see a better way? Thoughts are welcome... And im expecting trs80 to share his thoughts as soon as he gets the time.

Thanks for your time and thoughts,


    First they ignore you, then they laugh at you, then they fight you, then you win.
    -- Gandhi

Replies are listed 'Best First'.
Re: Extending MIME::Lite
by tachyon (Chancellor) on Sep 27, 2003 at 12:40 UTC

    The best way to extend a given function is generally to add args to the end of the arg list. This should not break old code but there is always the possibility that it will. So if you have lots of args you want to pass then passing an arg hash is a sound approach....BUT it looks like the new() function in Net::SMTP might cause you grief.

    If you trace through what occurs when you call send('smtp',@args) in Mime::Lite you effectively end up calling send_by_smtp(@args) which ends up with this:

    my $smtp = MIME::Lite::SMTP->new(@args)

    Given that this just calls the Mime::Lite::SMTP package which is a subclass of Net::SMTP.....this is where that call FINALLY ends up in Net::SMTP:

    sub new { my $self = shift; my $type = ref($self) || $self; my $host = shift if @_ % 2; my %arg = @_; my $hosts = defined $host ? [ $host ] : $NetConfig{smtp_hosts}; my $obj; my $h; foreach $h (@{$hosts}) { $obj = $type->SUPER::new(PeerAddr => ($host = $h), PeerPort => $arg{Port} || 'smtp(25)', LocalAddr => $arg{LocalAddr}, LocalPort => $arg{LocalPort}, Proto => 'tcp', Timeout => defined $arg{Timeout} ? $arg{Timeout} : 120 ) and last; }

    It seems to me that there is an assumption there that you are going to break. First the first arg to new() for Net::SMTP needs to be ($server/$host) and worse the %2 means it only recognises the $host arg if an ODD number of args is passed to new..... It then goes on to assume that all following args are a hash presented as a flat list. Adding your single scalar (hash ref) will cause it to explode with an odd number of elements in hash assignment error. Also passing {Debug=>1} as suggested will cause Net::SMTP to try to connect to a HASH REF which just aint gonna work. Presumably you plan to override this function in Mime::Lite - looks like you will have to to me.

    Personally a standard coding practice around here is NEVER to write a function like func($scalar, @ary) - we always do func($scalar, \@ary) so we can easily add $forgot_this, $forgot_that to the end of the arg list func( $scalar, \@ary, $forgot_this, $forgot_that ). As soon as you code a trailing array into a function call you are screwed if you need to extend it without causing major dramas (ie breaking just about everything that calls that func)......




      I'm thinking of intercepting the hashref before calling new. So that

      require Net::SMTP; my $smtp = MIME::Lite::SMTP->new(@args) or Carp::croak("Failed to connect to mail server: $!\n");

      becomes something like...

      require Net::SMTP; my $smtp; if (ref $args[0]) { $opts = shift @args; # handle special cases, and finally resulting in a usable valu +e # in $smtp. } else { $smtp = MIME::Lite::SMTP->new(@args) or Carp::croak("Failed to connect to mail server: $!\n"); }

      So before MIME::Lite::SMTP even gets called @args has been precleaned as appropriate. This would allow special cases like having a list of SMTP hosts that are tried until one lets us in, or handling authentication, or turning Debug on in Net::SMTP without having to do the ugly Net::SMTP->new(undef,Debug=>1) in the user code (inside MIME::Lite its ok to do this, as in there its unlikely to cause trouble to a newbie).

      Also I agree quite a bit with your points about positional arguments. The only quibble I have is from Perls flexibility with arguments and weakish typing. You can often extend positional arguments by getting creative with types. For instance the trick above works out because its extremely unlikely that somebody is using a blessed reference as a hostname. In fact I could get a lot more picky and check that the ref is a HASH and that if it is a hash that stringification is not overloaded. This would result in total backwards compatibility while still allowing extension.



        First they ignore you, then they laugh at you, then they fight you, then you win.
        -- Gandhi

        The else doesn't need to be there. The $smtp is still going to need to be created in the same way (or at least it seems so to me). The authentication action occurs after the Net::SMTP object has been created, which was part of my initial concern when adding support to MIME::Lite. I am still working through why Net::SMTP doesn't allow for the authentication arguments in the object creation. It would seem that Net::SMTP should perhaps be the one that bends and allows for the authenication to be passed into it on object creation.

        If we moved this logic into the Net::SMTP module, what would happen if we did this right before we return the Net::STMP object:
        if (defined $arg{User}) { $obj->auth($arg{User},$arg{Password}); }
        inside of the Net::SMTP new? Then when we create a new Net::SMTP call we do it as:
        MIME::Lite->send('smtp', $out_smtp , Port =>$smtp_port , Timeout=>60 , Debug => $debug , Hello => '', User => 'trs80', Password => 'blah' );
        My simple test indicates that it works, but I need to carefully review the side effects of performing the auth in the object creation.

        Placing the change inside of Net::SMTP makes more sense in that it keeps the logic in the right problem space and thereby making it benefical to a larger user base.
        This reply is to address the change if the modification takes place in MIME::Lite vs. Net::SMTP as my previous post suggests. I think the change in behavior should occur at the send method rather then the send_by_smtp. This would allow future modifications of send methods to accounted for in the same manner. So here is my proposed revised sub send:
        # add new global that the top my $SenderOpts = ''; sub send { my $self = shift; if (ref($self)) { ### instance method: my ($method, @args); if (@_) { ### args; use them just t +his once $method = 'send_by_' . shift; @args = @_; } else { ### no args; use defaults $method = "send_by_$Sender"; @args = @{$SenderArgs{$Sender} || []}; } $self->verify_data if $AUTO_VERIFY; ### prevents missing part +s! return $self->$method(@args); } else { ### class method: if (@_) { my @old = ($Sender, @{$SenderArgs{$Sender}}); $Sender = shift; if ( ref($_[0]) eq 'HASH') { $SenderOpts = shift; } $SenderArgs{$Sender} = [@_]; ### remaining args return @old; } else { Carp::croak "class method send must have HOW... arguments\n" +; } } }
        Then below in our send_by_smtp method we add:
        if ($SenderOpts) { $smtp->auth( $SenderOpts->{auth_username}, $SenderOpts->{auth +_password} ); }
        directly after our $smtp object is created.

Re: Extending MIME::Lite
by simonm (Vicar) on Sep 27, 2003 at 19:07 UTC
    I'd argue for what I understand to be trs80's revised proposal -- add this functionality to Net::SMTP instead of changing MIME::Lite.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://294634]
Approved by barrd
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (7)
As of 2018-06-22 17:36 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (124 votes). Check out past polls.