| < Day Day Up > |
|
Now, let’s look at a simple SMTP client written in Ruby. The SMTP client is implemented as a class that provides a set of methods to send an e-mail to a specified recipient (see Listing 20.12).
Four methods are provided by the Mailer class for e-mail transport. These are init, dialog, send, and finish. Only method dialog is hidden in the class (a protected method), and is utilized by the Mailer class to implement the actual SMTP protocol. We investigate each of these methods in their order of appearance within a sample use. One item to note before the methods is the creation of global class variables. In this case (lines 8–13), we define a set of instance variables and initialize them each to nil. We know these to be instance variables because each is preceded by a ‘@’ character. These variables are global to the class, and retain their value statically between method calls. This is important because we want to be able to define the socket variable within the init method, and still have it available when we later call the send method. Other types of Ruby variables include class variables, preceded by “@@” and pure global variables, preceded by “$”.
Listing 20.12 Ruby language SMTP client source.
1 require ‘socket’ 2 3 class Mailer 4 5 # 6 # Globals defined within the init method. 7 # 8 @subject = nil 9 @sender = nil 10 @recipient = nil 11 @content_type = nil 12 @contents = nil 13 @sock = 0 14 15 16 # 17 # Initialize the internal mail variables 18 # 19 def init( subject, sender, recipient, 20 content_type, contents ) 21 22 @subject = subject 23 @sender = sender 24 @recipient = recipient 25 @content_type = content_type 26 @contents = contents 27 28 end 29 30 31 # 32 # Perform a dialog with the SMTP server 33 # 34 def dialog( command, expected_response ) 35 36 # Only send the command if the caller provided one. 37 if command != nil 38 39 @sock.write( command ) 40 41 end 42 43 # Only check for a response if the user defined the 44 # expected response code. 45 if expected_response != nil 46 47 # Get a single line from the connection 48 line = @sock.gets 49 50 # Check the received line with the expected 51 # response code 52 if line[0..expected_response.length-1] != 53 expected_response 54 55 # Not what was expected, raise an exception. 56 raise 57 58 end 59 60 end 61 62 end 63 protected:dialog 64 65 66 # 67 # Send the mail based upon the initialized parameters 68 # 69 def send 70 71 mail_server = @recipient.split(‘@’)[1] 72 73 @sock = TCPSocket::open(mail_server, "mail") 74 75 # Look for initial e-mail salutation 76 self::dialog( nil, "220" ) 77 78 # Send HELO and await response 79 self::dialog( "HELO thisdomain.com\n", "250" ) 80 81 # Send "MAIL FROM" command and await response 82 string = "MAIL FROM: " + @sender + "\n" 83 self::dialog( string, "250" ) 84 85 # Send "RCPT TO" command and await response 86 string = "RCPT TO: " + @recipient + "\n" 87 self::dialog( string, "250" ) 88 89 # Send "DATA" to start the message body 90 self::dialog( "DATA\n", "354" ) 91 92 # Send out the mail headers (from/to/subject) 93 string = "From: " + @sender + "\n" 94 self::dialog( string, nil ) 95 96 string = "To: " + @recipient + "\n" 97 self::dialog( string, nil ) 98 99 string = "Subject: " + @subject + "\n" 100 self::dialog( string, nil ) 101 102 # Send the content type 103 string = "Content-Type: " + @content_type + "\n" 104 self::dialog( string, nil ) 105 106 # Send the actual message body 107 self::dialog( @contents, nil ) 108 109 # Send the end-of-email indicator 110 self::dialog( "\n.\n", "250" ) 111 112 # Finally, close out the session 113 self::dialog( "QUIT\n", "221" ) 114 115 rescue 116 117 puts "Couldn’t send mail" 118 @sock.close 119 120 end 121 122 123 def finish 124 125 @sock.close 126 127 end 128 129 130 end
The first function used by mail users is the init method. The application will create an instance of the Mailer class using Mailer::new. With the new instance, the init method is called to specify the subject, sender, recipient, content_type, and message body (contents). As shown in lines 19–28, these variables are copied from the application into the class instance variables and are bound to this instance of the class.
The send method is the next method invoked by the user (lines 69–120). This method implements SMTP and performs all communication with the SMTP server. The first step, at line 71, is to extract the domain name to which we’re connecting (the target mail server). This is taken from the recipient mail address using the split method, using the ‘@’ character to split the recipient username from the target domain name. For example, “mtj@mtjones.com” is split into an array of two elements, “mtj” and “mtjones.com”. Note that at line 71, we use only the second element, and store this to the mail_server local variable. Variable mail_server is then used at line 73 to create the TCP socket and connect it to the server using TCPSocket::open. The remainder of the send method is SMTP-specific communication to the mail server. This was discussed previously in Chapter 15, Software Patterns Introduction.
One item to note in the send method is the use of the rescue statement. If a method within the send method’s calls-chain raises an error (using the raise statement), then execution continues immediately at the rescue section. In this section (at line 115), we close the socket to end the session using the close method.
The dialog method (lines 34–63) is a protected method that performs the communication transactions with the SMTP server. Recall that SMTP commanding is command/response-based. If the caller provides a command, it is communicated to the SMTP server using the write method (lines 37–41). If a response is expected (lines 45–60), then it is retrieved using the gets method. The dialog method also checks the response from the expected numeric code (lines 52 and 53) as defined by the caller (expected_response), and if a different code is found, an exception is raised (line 56). Otherwise, the dialog method returns and the send method continues with the SMTP transactions.
The final method in the Mailer class (see Listing 20.12) is the finish method. This method is a cleanup method that closes the client socket using the close method. The finish method is the final method to be called once the SMTP session is complete.
Let’s now look at a sample usage of the Mailer class. In Listing 20.13, a simple application that utilizes the Mailer class to send an e-mail is shown. At line 4, we create a new instance of the Mailer class using Mailer::new. We store this new instance in a variable called mail. At lines 6–10, we call init and initialize the instance variables with our set of values to prepare for sending a new e-mail. We specify (in order) the subject, sender, recipient, content_type, and contents. Note that in this instance, we specify the content_type as “text/html”. Note at line 10, we specify an HTML-encoded e-mail. Therefore, the content_type must be “text/html” to permit the receiving mail client to properly render the e-mail. If we were sending pure text rather than HTML, we’d use “text/plain” as the content_type. At line 11, we call the send method to send the e-mail, and, finally, at line 13, we close out the SMTP session by calling the finish method.
Listing 20.13 Instantiating a new SMTP client.
1 # 2 # Create a new mailer and send an e-mail 3 # 4 mail = Mailer::new 5 6 mail.init( "The Subject", 7 "tim@mtjones.com", 8 "mtj@mtjones.com", 9 "text/html", 10 "<HTML><BODY><H1>:Your mail</H1></BODY></HTML>" ) 11 mail.send 12 13 mail.finish
| < Day Day Up > |
|