简体   繁体   English

使用现有 InputStream 作为附件内容使用 javax.mail 发送电子邮件

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

Is it possible to send an email using javax.mail and using an “existing” InputStream for the email message attachment content?是否可以使用javax.mail并使用“现有” InputStream作为电子邮件附件内容发送电子邮件?

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: InputStreamDataSource实现如下:

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. DataSource提供方法getInputStream()以获取电子邮件附件内容的InputStream

If I return a "new" InputStream which does not depend on an "existing" InputStream then it works fine.如果我返回一个“新” InputStream不依赖于“现有” InputStream然后正常工作。 But if I return an “existing” InputStream then the email message is delivered with a zero-byte attachment.但是如果我返回一个“现有的” InputStream ,那么电子邮件消息将与一个零字节附件一起发送。

Is it possible to send an email using javax.mail , and use an “existing” InputStream for the email message attachment content?是否可以使用javax.mail发送电子邮件,并使用“现有” InputStream作为电子邮件附件内容?

EDIT:编辑:

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

TL;DR use a ByteArrayDataSource TL;DR 使用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必须深入研究 Oracle 的源代码... 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:当前的 java 邮件实现在输入流上运行了 2 次:

  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 )首先确定是否应该将标题“Content-Transfer-Encoding”设置为 7 位或 8 位(请参阅Content Transfer Encoding 7bit 或 8 位
  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. ...哪种情况很糟糕,因为整个流(在慢速连接上可能有数百 MB)将被读取两次 ...并且对于一旦读取就“消耗”的流会导致这个问题。


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?!) ...按照这个顺序,而不是相反...因为出于某种原因setDataHandler内部调用另一个方法invalidateContentHeaders再次清除"Content-Transfer-Encoding"标头(wtf?!)

Sounded great, the mail was sent, hooray!!!听起来不错,邮件已发送,万岁!!! :D ... :( see next :D ... :( 见下


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.在这个蹩脚的 java 邮件代码中进行了长时间的搜索和研究之后,我找到了它,它们将InputStream管道化为LineOutputStream ,它改变了二进制数据的行尾。 Meh.嗯。 The java mail implementation is really a mess. java邮件实现真的是一团糟。 :/ :/

I rewrited your InputStreamDataSource class, and it works for me.我重写了你的 InputStreamDataSource 类,它对我有用。

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");
    }
}

If the InputStream contains mime headers then use the javax.mail.internet.MimeBodyPart(InputStream) constructor.如果InputStream包含 mime 标头,则使用javax.mail.internet.MimeBodyPart(InputStream)构造函数。 You don't need to use a custom DataSource class.您不需要使用自定义DataSource类。

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.否则,如果InputStream只是没有标题的正文,则将流转换为字节数组并使用javax.mail.internet.MimeBodyPart(InternetHeaders, byte[])构造函数提供标题。

I solved it converting the InputStream to a byte array and converting it to Base64 format.我解决了将InputStream转换为字节数组并将其转换为 Base64 格式的问题。

//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);

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.在 mimeType 中使用附件的 mime 类型。 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
    }

The current java mail implementation goes over the input stream twice: the first pass to detect the encoding for the data and the second one to send the data.当前的 java 邮件实现两次遍历输入流:第一遍检测数据的编码,第二遍发送数据。

You can prevent the first pass if you specify the encoding using the EncodingAware interface.如果您使用 EncodingAware 接口指定编码,则可以防止第一次通过。 The supplied DataSource should implement this interface.提供的 DataSource 应该实现这个接口。 Here is an example:下面是一个例子:

public class AttachementDataSource implements javax.activation.DataSource, javax.mail.EncodingAware {

    private final InputStreamSource inputStreamSource; 
    
    public AttachementDataSource(InputStreamSource inputStreamSource) {
        this.inputStreamSource = inputStreamSource;
    }
    
    @Override
    public InputStream getInputStream() throws IOException {
        return inputStreamSource.getInputStream();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Read-only javax.activation.DataSource");
    }

    @Override
    public String getContentType() {
        return "application/octet-stream";
    }

    @Override
    public String getName() {
        return "inline";
    }

    @Override
    public String getEncoding() {
        return "base64";
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM