Send email with javax.mail using an existing InputStream as attachment content

允我心安 提交于 2019-12-10 13:36:50

问题


Is it possible to send an email using javax.mail and using an “existing” InputStream for the email message attachment content?

Currently I am building the email message as follows:

final MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Subject line");

final Multipart multipartContent = new MimeMultipart();

    final MimeBodyPart textPart = new MimeBodyPart();
    textPart.setText("Message body");
    multipartContent.addBodyPart(textPart);

    final MimeBodyPart attachmentPart = new MimeBodyPart();
    final DataSource source = new InputStreamDataSource("text/plain", "test.txt", new ByteArrayInputStream("CONTENT INPUT STREAM".getBytes()));
    attachmentPart.setDataHandler(new DataHandler(source));
    attachmentPart.setFileName("text.txt");
    multipartContent.addBodyPart(attachmentPart);

message.setContent(multipartContent);

InputStreamDataSource is implemented as follows:

public class InputStreamDataSource implements DataSource
{
    private final String contentType;
    private final String name;
    private final InputStream inputStream;

    public InputStreamDataSource(String contentType, String name, InputStream inputStream)
    {
        this.contentType = contentType;
        this.name = name;
        this.inputStream = inputStream;
    }

    public String getContentType()
    {
        return contentType;
    }

    public String getName()
    {
        return name;
    }

    public InputStream getInputStream() throws IOException
    {
        System.out.println("CALLED TWICE: InputStreamDataSource.getInputStream()");
        return new BufferedInputStream(inputStream);
        //return new ByteArrayInputStream("THIS 'NEW' INPUT STREAM WORKS BUT 'EXISTING' INPUT STREAM RESULTS IN ZERO-BYTE ATTACHMENT".getBytes());
    }

    public OutputStream getOutputStream() throws IOException
    {
        throw new UnsupportedOperationException("Not implemented");
    }
}

The DataSource provides method getInputStream() to get the InputStream for the email message attachment content.

If I return a "new" InputStream which does not depend on an "existing" InputStream then it works fine. But if I return an “existing” InputStream then the email message is delivered with a zero-byte attachment.

Is it possible to send an email using javax.mail, and use an “existing” InputStream for the email message attachment content?


回答1:


If the InputStream contains mime headers then use the javax.mail.internet.MimeBodyPart(InputStream) constructor. You don't need to use a custom DataSource class.

Otherwise, if the InputStream is just the body without headers then convert the stream into a byte array and use the javax.mail.internet.MimeBodyPart(InternetHeaders, byte[]) constructor to provide your headers.




回答2:


I rewrited your InputStreamDataSource class, and it works for me.

class InputStreamDataSource implements DataSource {
String contentType;
String name;

byte[] fileData;

public InputStreamDataSource(String contentType, String name, InputStream inputStream) throws IOException {
    this.contentType = contentType;
    this.name = name;
    /**
     * It seems DataSource will close inputStream and reopen it.
     * I converted inputStream to a byte array, so it won't be closed again.
     */
    fileData = IOUtils.toByteArray(inputStream);
}

public String getContentType() {
    return contentType;
}

public String getName() {
    return name;
}

public InputStream getInputStream() throws IOException {
    /**
     * Convert byte array back to inputStream.
     */
    return new ByteArrayInputStream(fileData);
}

public OutputStream getOutputStream() throws IOException {
    throw new UnsupportedOperationException("Not implemented");
}

}




回答3:


I solved it converting the InputStream to a byte array and converting it to Base64 format.

//get file name
String fileName = ...;
//get content type
String fileContentType = ...;
//get file content
InputStream fileStream = ...;

//convert to byte array
byte[] fileByteArray = IOUtils.toByteArray(fileStream);
//and convert to Base64
byte[] fileBase64ByteArray = java.util.Base64.getEncoder().encode(fileByteArray);

//manually define headers
InternetHeaders fileHeaders = new InternetHeaders();
fileHeaders.setHeader("Content-Type", fileContentType + "; name=\"" + fileName + "\"");
fileHeaders.setHeader("Content-Transfer-Encoding", "base64");
fileHeaders.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

//build MIME body part
MimeBodyPart mbp = new MimeBodyPart(fileHeaders, fileBase64ByteArray);
mbp.setFileName(fileName);

//add it to the multipart
multipart.addBodyPart(mbp);



回答4:


EDIT:

see https://community.oracle.com/thread/1590625

TL;DR use a ByteArrayDataSource


One has to delve into Oracle's source code... https://java.net/projects/javamail/sources/mercurial/content/mail/src/main/java/javax/mail/internet/MimeBodyPart.java

The current java mail implementation goes 2 times over the input stream:

  1. First to determine whether it should set the header "Content-Transfer-Encoding" to 7 or 8 bits (see Content Transfer Encoding 7bit or 8 bit)
  2. Then a second time when it actually writes the message

...which kind of sucks because the whole stream (maybe hundreds of MB over a slow connection) will be read two times ...and leads to exactly this issue for streams that are "consumed" once read.


The first "workaround" I tried is to specify the headers yourself:

attachmentPart.setDataHandler(new DataHandler(source));
attachmentPart.setHeader("Content-Transfer-Encoding", "8bit");
attachmentPart.setHeader("Content-Type", ds.getContentType() + "; " + ds.getName());

...and in that order, and not the other way round ...because for some reason setDataHandler calls internally another method invalidateContentHeaders which clears the "Content-Transfer-Encoding" header again (wtf?!)

Sounded great, the mail was sent, hooray!!! :D ... :( see next


Attachment send ...but broken

The received file in my mail server is broken. Huh. Why?!. After a long search and delving again in this crappy java mail code, I found it, they pipe the InputStream into a LineOutputStream which changes the line endings of your binary data. Meh. The java mail implementation is really a mess. :/




回答5:


I use this code for sending email with web downloaded attachment. You can easily edit it for your purpose. In mimeType use mime type of your attachment. Happy coding.

try {

        Message message = new MimeMessage(session);
        message.setFrom(new InternetAddress(
                "sender@gmail.com"));
        message.setRecipients(Message.RecipientType.TO,
                InternetAddress.parse("reciever@gmail.com"));
        message.setSubject("subject");

        Multipart multipart = new MimeMultipart();

        URL url = new URL(url);

        InputStream is = url.openStream();
        MimeBodyPart bodyPart = new MimeBodyPart(is);

        multipart.addBodyPart(bodyPart);

        message.setContent(multipart);
        message.addHeader("Content-Type", mimeType);
        Transport.send(message);
        logger.info("SENT to" + message.getRecipients(RecipientType.TO));

    } catch (MessagingException e) {
        //some implementation
    }


来源:https://stackoverflow.com/questions/34009587/send-email-with-javax-mail-using-an-existing-inputstream-as-attachment-content

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!