james源码解析(四)

在测试过程中发现了james一个不那么友好的地方,略微做一些修改

在测试james的过程中,我尝试向general-subscribe@james.apache.org(james的一个邮件列表)发送邮件,结果邮件始终无法送达,debug之后发现是apache服务器拒绝了命令

拒绝的是 helo命令和echo 命令会传输一个 hostname,默认情况取得是主机名称,因此给拒绝了。

修改 mailetcontainer.xml中的RemoteDelivery,添加一个属性 heloName,最终结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
<processor state="relay" enableJmx="true">
<mailet match="All" class="RemoteDelivery">
<outgoingQueue>outgoing</outgoingQueue>
<delayTime>5000, 100000, 500000</delayTime>
<maxRetries>3</maxRetries>
<maxDnsProblemRetries>0</maxDnsProblemRetries>
<deliveryThreads>10</deliveryThreads>
<sendpartial>true</sendpartial>
<bounceProcessor>bounces</bounceProcessor>
<heloName>smtp.domain.com</heloName>
</mailet>
</processor>

本以为这样就可以了,继续测试发现出现了另一个错误

Client host rejected: cannot find your reverse hostname

查了一下该问题无解,因为要解决该问题,需要给出口ip做反向域名解析,绑定到heloName上,然而这是一项收费服务,而且还很贵。


邮件发不出去没关系,但是消息提醒需要有啊,在测试过程中,我确实收到过一次错误邮件,但是邮件内容只有以下信息有用

Error message:
Too many retries failure. Bouncing after 3 retries.

意思是重试了三次依然失败,但是没有错误原因,这就很不方便。因此我准备改造一下


投递出错的处理在DeliveryRunnable的第153行 handleTemporaryFailure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@VisibleForTesting
void attemptDelivery(Mail mail) throws MailQueue.MailQueueException {
ExecutionResult executionResult = mailDelivrer.deliver(mail);
switch (executionResult.getExecutionState()) {
case SUCCESS:
outgoingMailsMetric.increment();
configuration.getOnSuccess()
.ifPresent(Throwing.consumer(onSuccess -> mailetContext.sendMail(mail, onSuccess)));
break;
case TEMPORARY_FAILURE:
handleTemporaryFailure(mail, executionResult);
break;
case PERMANENT_FAILURE:
handlePermanentFailure(mail, executionResult);
break;
}
}

里面负责判断重试次数,如果达标,则交给Bouncer处理,Bouncer会修改邮件状态,然后转给别的处理器处理。默认配置下最终转给了DSNBounce

研究了一下代码,在Bouncer的第62行,就已经把真正有用的错误堆栈信息给处理掉了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void bounce(Mail mail, Exception ex) {
if (!mail.hasSender()) {
LOGGER.debug("Null Sender: no bounce will be generated for {}", mail.getName());
} else {
if (configuration.getBounceProcessor() != null) {
computeErrorCode(ex).ifPresent(mail::setAttribute);
mail.setAttribute(new Attribute(DELIVERY_ERROR, AttributeValue.of(getErrorMsg(ex))));
try {
mailetContext.sendMail(mail, configuration.getBounceProcessor());
} catch (MessagingException e) {
LOGGER.warn("Exception re-inserting failed mail: ", e);
}
} else {
bounceWithMailetContext(mail, ex);
}
}
}

那就只有改这个类,但是这个类是直接在RemoteDelivery里写死的new方法创建,没办法动态注入

1
2
3
4
5
6
7
8
9
10
11
12
13
public void init() throws MessagingException {
configuration = new RemoteDeliveryConfiguration(getMailetConfig(), domainList);
queue = queueFactory.createQueue(configuration.getOutGoingQueueName());
deliveryRunnable = new DeliveryRunnable(queue,
configuration,
dnsServer,
metricFactory,
getMailetContext(),
new Bouncer(configuration, getMailetContext()));
if (startThreads == ThreadState.START_THREADS) {
deliveryRunnable.start();
}
}

james大概是不希望这里能够自由扩展,但是没关系,反正是有源码的,改一下重新打包就是,修改成如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

private void handleTemporaryFailure(Mail mail, ExecutionResult executionResult) throws MailQueue.MailQueueException {
if (!mail.getState().equals(Mail.ERROR)) {
mail.setState(Mail.ERROR);
DeliveryRetriesHelper.initRetries(mail);
mail.setLastUpdated(dateSupplier.get());
}
mail.setAttribute(new Attribute(IS_DELIVERY_PERMANENT_ERROR, AttributeValue.of(false)));
int retries = DeliveryRetriesHelper.retrieveRetries(mail);

if (retries < configuration.getMaxRetries()) {
reAttemptDelivery(mail, retries);
} else {
LOGGER.debug("Bouncing message {} after {} retries", mail.getName(), retries);
bouncer.bounce(mail, new Exception("Too many retries failure. Bouncing after " + retries + " retries.\n" + executionResult.getException().map(e -> {
if (e instanceof MessagingException) {
return e.getMessage() + "\n" + ((MessagingException) e).getNextException().getMessage();
}
return e.getMessage();
}).orElse(""), executionResult.getException().orElse(null)));
}
}

尝试打包后失败,原因是代码格式未通过校验。相关错误是import的位置不对和代码缩减使用了tab,处理一下就行能顺利打包了。

如果之前已经打过包的,只需要在server/mailet/mailets和要使用的构建版本打两次包就行

我这里用的docker,所以需要的是server/apps/jpa-app/target下的jib-image.tar,我还顺便改了一下image的标签


james源码解析(四)
http://blog.inkroom.cn/2022/11/15/0A214P.html
作者
inkbox
发布于
2022年11月15日
许可协议