繁体   English   中英

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

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

是否可以使用javax.mail并使用“现有” InputStream作为电子邮件附件内容发送电子邮件?

目前我正在构建电子邮件消息如下:

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

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

DataSource提供方法getInputStream()以获取电子邮件附件内容的InputStream

如果我返回一个“新” InputStream不依赖于“现有” InputStream然后正常工作。 但是如果我返回一个“现有的” InputStream ,那么电子邮件消息将与一个零字节附件一起发送。

是否可以使用javax.mail发送电子邮件,并使用“现有” InputStream作为电子邮件附件内容?

编辑:

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

TL;DR 使用ByteArrayDataSource


必须深入研究 Oracle 的源代码... https://java.net/projects/javamail/sources/mercurial/content/mail/src/main/java/javax/mail/internet/MimeBodyPart.java

当前的 java 邮件实现在输入流上运行了 2 次:

  1. 首先确定是否应该将标题“Content-Transfer-Encoding”设置为 7 位或 8 位(请参阅Content Transfer Encoding 7bit 或 8 位
  2. 然后第二次实际写入消息时

...哪种情况很糟糕,因为整个流(在慢速连接上可能有数百 MB)将被读取两次 ...并且对于一旦读取就“消耗”的流会导致这个问题。


我尝试的第一个“解决方法”是自己指定标题:

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

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

听起来不错,邮件已发送,万岁!!! :D ... :( 见下


附件发送...但坏了

我的邮件服务器中收到的文件已损坏。 呵呵。 为什么?! . 在这个蹩脚的 java 邮件代码中进行了长时间的搜索和研究之后,我找到了它,它们将InputStream管道化为LineOutputStream ,它改变了二进制数据的行尾。 嗯。 java邮件实现真的是一团糟。 :/

我重写了你的 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");
    }
}

如果InputStream包含 mime 标头,则使用javax.mail.internet.MimeBodyPart(InputStream)构造函数。 您不需要使用自定义DataSource类。

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

我解决了将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);

我使用此代码发送带有网络下载附件的电子邮件。 您可以根据自己的目的轻松编辑它。 在 mimeType 中使用附件的 mime 类型。 快乐编码。

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
    }

当前的 java 邮件实现两次遍历输入流:第一遍检测数据的编码,第二遍发送数据。

如果您使用 EncodingAware 接口指定编码,则可以防止第一次通过。 提供的 DataSource 应该实现这个接口。 下面是一个例子:

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