JavaMail 传输大型二进制 MIME 消息(rfc3030)

标签 java jakarta-mail

我需要与其中一台邮件服务器进行交换,使用 RFC3030对于大型 mime 消息。 原来的任务是:如果MIME消息大小> 80MB,我需要使用RFC3030。

我怎么理解,JavaMail 不能“从盒子里”做到这一点?

也许我可以为 JavaMail 创建一些实现 RFC3030 的处理程序或扩展?

请帮忙。我不知道该怎么办。

最佳答案

快速浏览SMTPTransport确认:普通的旧 JavaMail 不支持 BDAT,它总是尝试使用 DATA 命令发送,如下所示:

this.message.writeTo(data(), ignoreList);
finishData();

如果您不害怕(并且没有法律理由不这样做)修改核心 JDK 类,您可以重写方法 data() 和 finishData(),因为它们都受到保护(源代码来自 here ):

/**
* Send the <code>DATA</code> command to the SMTP host and return
* an OutputStream to which the data is to be written.
*
* @since JavaMail 1.4.1
*/
protected OutputStream data() throws MessagingException {
  assert Thread.holdsLock(this);
  issueSendCommand("DATA", 354);
  dataStream = new SMTPOutputStream(serverOutput);
  return dataStream;
}

/**
* Terminate the sent data.
*
* @since JavaMail 1.4.1
*/
protected void finishData() throws IOException, MessagingException {
  assert Thread.holdsLock(this);
  dataStream.ensureAtBOL();
  issueSendCommand(".", 250);
}

为了支持RFC3030 ,我建议您首先将整个消息缓冲到 ByteArrayOutputStream 中。您需要它来确定要发送的消息的大小。如果“小”-> 执行 SMTPTransport会做。如果“大”,则将字节分割成 block 并将它们发送到 BDAT风格。我建议以 0 长度结束 LAST BDAT和代码

protected void finishData() throws IOException, MessagingException {
  assert Thread.holdsLock(this);
  dataStream.ensureAtBOL();
  issueSendCommand("BDAT 0 LAST", 250);
}

--编辑--

这是一个非常简单的第一种方法,还有很多事情需要做得更好。最重要的是 outputStream 的即用即分块实现message.writeTo() 期间发送数据 block 不断地填充它。填充一个大字节[]只是为了稍后将其分割成 block ,这在内存占用方面是非常非常糟糕的。但这种方式更容易阅读,因为所有分块和发送都发生在一个地方。请注意,此示例使用反射来访问 serverOutput Oracle 中的字段SMTPTransport 。因此,对于任何新版本的 JavaMail,它都可能随时崩溃而不会发出任何警告。另外,我的异常处理暂时不遵循 RFC-3030,因为如果 BDAT 失败,则不会执行 RSET。

package de.janschweizer;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.lang.reflect.Field;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.URLName;

import com.sun.mail.smtp.SMTPOutputStream;

public class SMTPTransport extends com.sun.mail.smtp.SMTPTransport {

    //We can have our own copy - it's only used in the methods we override anyways.
    private SMTPOutputStream dataStream;

    private ByteArrayOutputStream baos;

    public SMTPTransport(Session session, URLName urlname, String string, boolean bool) {
        super(session, urlname, string, bool);
    }

    public SMTPTransport(Session session, URLName urlname) {
        super(session, urlname);            
    }

    protected OutputStream data() throws MessagingException {
        assert(Thread.holdsLock(this));
        if(!supportsExtension("CHUNKING")) {
            return super.data();
        }
        baos = new ByteArrayOutputStream();
        this.dataStream = new SMTPOutputStream(baos);       
        return this.dataStream;
    }

    protected void finishData() throws IOException, MessagingException {
        assert(Thread.holdsLock(this));
        if(!supportsExtension("CHUNKING")) {
            super.finishData();
            return;
        }
        this.dataStream.ensureAtBOL();
        dataStream.flush();
        BufferedReader br = new BufferedReader(new StringReader(new String(baos.toByteArray())));

        try {
            //BAD reflection hack
            Field fServerOutput = com.sun.mail.smtp.SMTPTransport.class.getDeclaredField("serverOutput");
            fServerOutput.setAccessible(true);
            OutputStream os = (OutputStream)fServerOutput.get(this);

            //Do the Chunky
            ByteArrayOutputStream bchunk = new ByteArrayOutputStream();
            PrintWriter pw = new PrintWriter(bchunk);
            String line = br.readLine();
            int linecount = 0;
            while(line != null) {
                pw.println(line);
                if(++linecount % 5000 == 0) {
                    pw.flush();
                    byte[] chunk = bchunk.toByteArray();
                    sendChunk(os, chunk);
                    bchunk = new ByteArrayOutputStream();
                    pw = new PrintWriter(bchunk);
                }
                line = br.readLine();
            }
            pw.flush();
            byte[] chunk = bchunk.toByteArray();
            sendLastChunk(os, chunk);           
        } catch (Exception e) {
            throw new MessagingException("ReflectionError", e);
        }
    }

    private void sendChunk(OutputStream os, byte[] chunk) throws MessagingException, IOException {
        sendCommand("BDAT "+chunk.length);
        os.write(chunk);
        os.flush();     
        int rc = readServerResponse();
        if(rc != 250) {
            throw new MessagingException("Something very wrong");
        }
    }

    private void sendLastChunk(OutputStream os, byte[] chunk) throws MessagingException, IOException {
        sendCommand("BDAT "+chunk.length+" LAST");
        os.write(chunk);
        os.flush();     
        int rc = readServerResponse();
        if(rc != 250) {
            throw new MessagingException("Something very wrong");
        }
    }
}

有了这个 META-INF/javamail.providers

protocol=smtp; type=transport; class=de.janschweizer.SMTPTransport; vendor=Jan Schweizer;

关于JavaMail 传输大型二进制 MIME 消息(rfc3030),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33777595/

相关文章:

java - 序列化问题: org. springframework.dao.support.PersistenceExceptionTranslationInterceptor

java - 如何使用 java 将 Excel 转换为 XML?

java - 如何防止轻量级组件接收鼠标事件?

parsing - 在javamail中解析电子邮件中的 "from"地址

java - 阅读从 GMail 发送的邮件

javax.mail.SendFailedException

java - 使用 JavaMail 向多个接收方发送邮件

smtp - JavaMail SMTPSendFailedException

java - 在 Android 中调用 REST Web 服务并在字符串数据中获取 null

java - 如何去除Java持久化的无状态实体