April 18, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

WebMail in Java: Sending E-mail

  • December 17, 1999
  • By Benoît Marchal
  • Send Email »
  • More Articles »

In this article and the next one, I'll present a Web-based e-mail client. It is similar to Hotmail, allowing you to check e-mail over the Web. The client consists of two servlets (one for sending e-mails in this article and one for reading e-mails in the next article) that use JavaMail to communicate with the e-mail system.

For simplicity, presentation was kept minimalist, there is little error-checking and popular features, like an address-book, are missing. However the servlets handle e-mail attachments and demonstrate how to upload files to a web server.

If you are not familiar with JavaMail, you should read Java Meets E-mail first. To compile and run the servlets, install JavaMail and the Java Activation Framework (both available from java.sun.com). While downloading JavaMail, be sure to grab a copy of the POP3 provider (a separate download). You will also need a servlet-enabled Web server such as Jetty.

GET requests

The first servlet, WebSMTP, handles both GET and POST requests. For GET requests, it returns an HTML form to compose an e-mail, see Listing 1. This form contains an input field of type "file". Thanks to this special field, when the user hits the submit button, the browser uploads the file to the server.


Listing 1. doGet() method.

package com.psol.webpop3;

import java.io.*;
import java.util.*;
import java.text.*;
import javax.mail.*;
import javax.servlet.*;
import javax.activation.*;
import javax.servlet.http.*;
import javax.mail.internet.*;

public class WebSMTP
   extends HttpServlet
{
   protected static final String composeForm =
      "<HTML><HEAD\><TITLE>WebSMTP</TITLE></HEAD>" +
      "<BODY><FORM ACTION=\"{0}\" METHOD=\"POST\"" +
      "ENCTYPE=\"multipart/form-data\">" +
      "<B>From:</B> <INPUT NAME=\"from\"><BR>" +
      "<B>To:</B> <INPUT NAME=\"to\"><BR>" +
      "<B>Cc:</B> <INPUT NAME=\"cc\"><BR>" +
      "<B>Subject:</B> <INPUT NAME=\"subject\"><BR>" +
      "<TEXTAREA NAME=\"body\"></TEXTAREA><BR>" +
      "<INPUT TYPE=\"FILE\" NAME=\"attachment\"><BR>" +
      "<INPUT TYPE=\"SUBMIT\"></FORM></BODY></HTML>",
                                 okForm =
      "<HTML><HEAD><TITLE>WebSMTP</TITLE></HEAD>" +
      "<BODY><P>Mail sent successfully" +
      "<BR><A HREF=\"{0}\">Write another</A>" +
      "</BODY></HTML>";

   protected void printForm(String form,
                            HttpServletRequest request,
                            HttpServletResponse response)
      throws IOException
   {
      PrintWriter writer = response.getWriter();
      form = MessageFormat.format(form,
         new Object[] { request.getServletPath() });
      writer.print(form);
      writer.flush();
   }

   protected void doGet(HttpServletRequest request,
                        HttpServletResponse response)
      throws IOException
   {
      printForm(composeForm,request,response);
   }
   

POST requests

Handling the POST request is more involving, because you receive no help from the servlets library. To accommodate the file, the form data are sent using the multipart/form-data format, which is not supported by servlets! Forms are normally encoded in the application/x-www-form-urlencoded format which is the only format supported by servlets. We have to roll-up our sleeves and write our own algorithm to decode multipart/form-data. Listing 2 forwards the call to

doUpload()
, which takes care of decoding the request.


Listing 2: doPost() method.

   protected void doPost(HttpServletRequest request,
                         HttpServletResponse response)
      throws IOException
   {
      try
      {
         if(request.getContentType().
               startsWith("multipart/form-data"))
            doUpload(request,response);
         else
            response.sendError(
               HttpServletResponse.SC_NOT_FOUND);
      }
      catch(Exception e)
      {
         PrintWriter writer = response.getWriter();
         writer.println("<HR><B>Oops!</B><PRE>");
         e.printStackTrace(writer);
         writer.println("</PRE>");
         writer.flush();
      }
   }

Sending attachments

Before studying

doUpload()
, let's look at
doSendMessage()
which
doUpload()
calls eventually. With Internet mail, attachments are multipart e-mails. In JavaMail, it translates into a MimeMultipart object, see Listing 3. MimeMultipart groups several MimeBodyParts, one for the attachment plus one for the body of the message. However, when there is no attachment,
doSendMessage()
places the body directly in the message.


Listing 3. doSendMessage() method.

   protected void doSendMessage(HttpServletRequest request,
                                HttpServletResponse response,
                                Dictionary fields)
      throws IOException, MessagingException
   {
      Message msg =
         new MimeMessage(
            Session.getDefaultInstance(
               System.getProperties(),null));
      msg.setFrom(
         new InternetAddress(
            (String)fields.get("from")));
      InternetAddress[] tos =
         InternetAddress.parse((String)fields.get("to"));
      msg.setRecipients(Message.RecipientType.TO,tos);
      if(fields.get("cc") != null)
      {
         InternetAddress[] ccs =
             InternetAddress.parse((String)fields.get("cc"));
         msg.setRecipients(Message.RecipientType.CC,ccs);
      }
      msg.setSubject((String)fields.get("subject"));
      msg.setSentDate(new Date());
      if(null == fields.get("attachment"))
         msg.setText((String)fields.get("body"));
      else
      {
         BodyPart body = new MimeBodyPart(),
                  attachment =
                     (BodyPart)fields.get("attachment");
         body.setText((String)fields.get("body"));
         MimeMultipart multipart = new MimeMultipart();
         multipart.addBodyPart(body);
         multipart.addBodyPart(attachment);
         msg.setContent(multipart);
      }
      Transport.send(msg);
      printForm(okForm,request,response);
   }

Uploading a file to the Web server

With multipart/form-data, the browser sends the various fields in the HTML form separated by boundary lines. Each field includes a header followed by the field content separated by a blank line. Listing 4 illustrates what the from field may look like. The browser produces Listing 4 when the user enters, say, bmarchal@pineapplesoft.com in the from field. The content of the file field is the content of the file itself.


Listing 4. A field in multipart/form-data.

-----------------------------18901828217873
Content-Disposition: form-data; name="from"

bmarchal@pineapplesoft.com
-----------------------------18901828217873

doUpload()
decodes multipart/form-data requests. It loops through the fields and stores them as name/value pairs in a Dictionary object. Input fields (to, from, etc.) are stored as String objects while the file field is stored in a MimeBodyPart object. The later eventually becomes the e-mail attachment.

If you examine Listing 5, you will see that

doUpload()
works like a state machine: it goes through different states as it hits a boundary line, a field header, or a field content. Obviously, after decoding the request, it calls
doSendMessage()
.

doUpload()
uses the ByteArrayDataSource to load the file content in the MimeBodyPart object. Unfortunately, it is not possible to copy a byte array directly in a MimeBodyPart. Instead, you have to go through an object that implements the DataSource interface and call the
setDataHandler()
method on the MimeBodyPart. Listing 6 is ByteArrayDataSource, a simple class that wraps a byte array in a DataSource.


Listing 5. doUpload() method.

   public void doUpload(HttpServletRequest request,
                        HttpServletResponse response)
      throws IOException, MessagingException
   {
      String boundary =
         request.getHeader("Content-Type");
      int pos = boundary.indexOf('=');
      boundary = boundary.substring(pos + 1);
      boundary = "--" + boundary;
      ServletInputStream in =
         request.getInputStream();
      byte[] bytes = new byte[512];
      int state = 0;
      ByteArrayOutputStream buffer =
         new ByteArrayOutputStream();
      String name = null,
             value = null,
             filename = null,
             contentType = null;
      Dictionary fields = new Hashtable();

      int i = in.readLine(bytes,0,512);
      while(-1 != i)
      {
         String st = new String(bytes,0,i);
         if(st.startsWith(boundary))
         {
            state = 0;
            if(null != name)
            {
               if(value != null)
                  fields.put(name,
                     value.substring(0,
                           // -2 to remove CR/LF
                           value.length() - 2));
               else if(buffer.size() > 2)
               {
                  InternetHeaders headers =
                     new InternetHeaders();
                  MimeBodyPart bodyPart =
                     new MimeBodyPart();
                  DataSource ds =
                     new ByteArrayDataSource(
                        buffer.toByteArray(),
                        contentType,filename);
                  bodyPart.setDataHandler(
                     new DataHandler(ds));
                  bodyPart.setDisposition(
                     "attachment; filename=\"" +
                     filename + "\"");
                  bodyPart.setFileName(filename);
                  fields.put(name,bodyPart);
               }
               name = null;
               value = null;
               filename = null;
               contentType = null;
               buffer = new ByteArrayOutputStream();
            }
         }
         else if(st.startsWith(
            "Content-Disposition: form-data") &&
            state == 0)
         {
            StringTokenizer tokenizer =
               new StringTokenizer(st,";=\"");
            while(tokenizer.hasMoreTokens())
            {
               String token = tokenizer.nextToken();
               if(token.startsWith(" name"))
               {
                  name = tokenizer.nextToken();
                  state = 2;
               }
               else if(token.startsWith(" filename"))
               {
                  filename = tokenizer.nextToken();
                  StringTokenizer ftokenizer =
                     new StringTokenizer(filename,"\\/:");
                  filename = ftokenizer.nextToken();
                  while(ftokenizer.hasMoreTokens())
                     filename = ftokenizer.nextToken();
                  state = 1;
                  break;
               }
            }
         }
         else if(st.startsWith("Content-Type") &&
                 state == 1)
         {
            pos = st.indexOf(":");
            // + 2 to remove the space
            // - 2 to remove CR/LF
            contentType =
               st.substring(pos + 2,st.length() - 2);
         }
         else if(st.equals("\r\n") && state == 1)
            state = 3;
         else if(st.equals("\r\n") && state == 2)
            state = 4;
         else if(state == 4)
            value = value == null ? st : value + st;
         else if(state == 3)
            buffer.write(bytes,0,i);
         i = in.readLine(bytes,0,512);
      }

      doSendMessage(request,response,fields);
   }
}

Listing 6. ByteArrayDataSource Class.

class ByteArrayDataSource
   implements DataSource
{
   byte[] bytes;
   String contentType,
          name;

   ByteArrayDataSource(byte[] bytes,
                       String contentType,
                       String name)
   {
      this.bytes = bytes;
      if(contentType == null)
         this.contentType = "application/octet-stream";
      else
         this.contentType = contentType;
      this.name = name;
   }

   public String getContentType()
   {
      return contentType;
   }

   public InputStream getInputStream()
   {
      // remove the final CR/LF
      return new ByteArrayInputStream(
         bytes,0,bytes.length - 2);
   }

   public String getName()
   {
      return name;
   }

   public OutputStream getOutputStream()
      throws IOException
   {
      throw new FileNotFoundException();
   }
}

Compiling and running

To compile this example, you must copy Listings 1 to 6 in a single file called WebSMTP.java and compile it. Make sure you installed JavaMail and the other packages as explained in the introduction. Next, install the servlet on your server and start e-mailing the world!

Conclusion

JavaMail hides all the difficulties of sending e-mails from Java, therefore, it is not difficult to write an e-mail client as a servlet. The most difficult element in this servlet is the

doUpload()
method, whereby the browser can upload a file to the Web server.

About the author

Benoît Marchal is a software engineer and consultant based in Namur, Belgium, who has been working extensively in Java and XML. He runs his own software company, Pineapplesoft. He also likes teaching and writing; his book "XML by Example" has been published by Que.








Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel