【问题标题】:JavaMail Transmission of Large and Binary MIME Messages(rfc3030)大型和二进制 MIME 消息的 JavaMail 传输 (rfc3030)
【发布时间】:2016-02-20 01:02:22
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: java jakarta-mail


    【解决方案1】:

    快速查看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 那样做就可以了。如果“大”,则将字节分成块并以BDAT 样式发送。我建议以 0 lentgh LAST BDAT 和代码结尾

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

    -- 编辑--

    这是一个非常简单的第一种方法,还有很多事情需要做得更好。最重要的是,outputStream 发送数据块,而message.writeTo() 不断填充数据块的即用即用实现。填充一个大字节 [] 只是为了稍后将其分成块,这在内存占用方面非常非常糟糕。但是这样更容易阅读,因为所有的分块和发送都发生在一个地方。请注意,此示例使用反射来访问 Oracle 的 SMTPTransport 中的 serverOutput 字段。因此,任何新版本的 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;
    

    【讨论】:

    • 是的,这可能是正确的方法,尽管代码应该在执行任何操作之前检查服务器是否支持 CHUNKING 扩展。此外,您应该能够继承 SMTPTransport,创建自己的传输类型,使用 javamail.providers 文件将其打包到您自己的 jar 文件中,并使其与 JavaMail 一起工作,而无需更改 JavaMail 本身。
    • @BillShannon 是的,创建自己的实现和子类化听起来不错。太糟糕了,所有 dataStream、serverOutput 和 issueSendCommand 都是私有的。缺少完整的源代码副本或非常糟糕的反射黑客是一条死胡同。
    • @BillShannon 从头开始​​。无论如何,我们只需要 serverOutput 。以受保护的方式访问它可能会很好
    • 您可以使用 sendCommand 方法,但您必须将 BDAT 命令与单字节数组中的消息数据结合起来。您绝对不想使用 SMTPOutputStream,因为它会进行点引用,而这是您不想要的。你确实需要 CRLFOutputStream。如果您编写自己的 OutputStream,您可以用 BDAT 和消息数据填充缓冲区,使用 sendCommand 写入,读取响应,然后继续下一个缓冲区。我看到 Gmail 支持 CHUNKING,所以添加到 JavaMail 中可能是件好事。
    • 是的 - sendData 听起来不错。也许我会在 github 上试一试。
    猜你喜欢
    • 2012-06-19
    • 2015-01-07
    • 2015-05-01
    • 1970-01-01
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多