[英]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/1590625见https://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 次:
...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.