Add example code for smtp client (#10345)
Motivation: We don't have example code for smtp. Modifications: Add SmtpClient and SmtpClientHandler. Result: Provide an example for developers who want to write a smtp client.
This commit is contained in:
parent
85b72923d9
commit
03b6be774d
@ -113,6 +113,11 @@
|
||||
<artifactId>netty-codec-dns</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec-smtp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
|
110
example/src/main/java/io/netty/example/smtp/SmtpClient.java
Normal file
110
example/src/main/java/io/netty/example/smtp/SmtpClient.java
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
||||
* copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.smtp;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.MultithreadEventLoopGroup;
|
||||
import io.netty.channel.nio.NioHandler;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.smtp.DefaultSmtpRequest;
|
||||
import io.netty.handler.codec.smtp.SmtpCommand;
|
||||
import io.netty.handler.codec.smtp.SmtpRequest;
|
||||
import io.netty.handler.codec.smtp.SmtpRequestEncoder;
|
||||
import io.netty.handler.codec.smtp.SmtpResponse;
|
||||
import io.netty.handler.codec.smtp.SmtpResponseDecoder;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import static io.netty.handler.codec.smtp.SmtpCommand.*;
|
||||
|
||||
/**
|
||||
* A simple smtp client
|
||||
*/
|
||||
public class SmtpClient {
|
||||
|
||||
private static final String HOST = System.getProperty("host", "127.0.0.1");
|
||||
private static final int PORT = Integer.parseInt(System.getProperty("port", "25"));
|
||||
|
||||
private static final String USERNAME = "sample-user@test.com";
|
||||
private static final String PASSWORD = "sample-password";
|
||||
|
||||
private static final String SENDER = "sample-sender@test.com";
|
||||
private static final String RECEIVER = "sample-receiver@test.com";
|
||||
|
||||
private static final String content = "From: " + SENDER + "\r\n" +
|
||||
"To: " + RECEIVER + "\r\n" +
|
||||
"Subject: test\r\n" +
|
||||
"\r\n" +
|
||||
"This is a test message.\r\n" +
|
||||
".\r\n";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SmtpClientHandler handler = new SmtpClientHandler();
|
||||
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
|
||||
try {
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(group)
|
||||
.channel(NioSocketChannel.class)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
p.addLast(new SmtpRequestEncoder());
|
||||
p.addLast(new SmtpResponseDecoder(Integer.MAX_VALUE));
|
||||
p.addLast(handler);
|
||||
}
|
||||
});
|
||||
|
||||
ByteBuf mailContent = Unpooled.wrappedBuffer(content.getBytes(CharsetUtil.UTF_8));
|
||||
|
||||
// Start send smtp command.
|
||||
Future<SmtpResponse> f = handler.createResponseFuture(group.next())
|
||||
.thenCompose(v -> handler.send(req(EHLO, "localhost")))
|
||||
.thenCompose(r -> handler.send(req(AUTH, "login")))
|
||||
.thenCompose(r -> handler.send(req(EMPTY, encode(USERNAME))))
|
||||
.thenCompose(r -> handler.send(req(EMPTY, encode(PASSWORD))))
|
||||
.thenCompose(r -> handler.send(req(MAIL, String.format("FROM:<%s>", SENDER))))
|
||||
.thenCompose(r -> handler.send(req(RCPT, String.format("TO:<%s>", RECEIVER))))
|
||||
.thenCompose(r -> handler.send(req(DATA)))
|
||||
.thenCompose(r -> handler.sendMailContent(mailContent))
|
||||
.thenCompose(r -> handler.send(req(QUIT)))
|
||||
.future();
|
||||
|
||||
// Start connect.
|
||||
Channel ch = b.connect(HOST, PORT).sync().channel();
|
||||
f.await();
|
||||
ch.close();
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
private static String encode(String msg) {
|
||||
return Base64.getEncoder().encodeToString(msg.getBytes(CharsetUtil.UTF_8));
|
||||
}
|
||||
|
||||
private static SmtpRequest req(SmtpCommand command, CharSequence... arguments) {
|
||||
return new DefaultSmtpRequest(command, arguments);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
||||
* copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.smtp;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.smtp.DefaultLastSmtpContent;
|
||||
import io.netty.handler.codec.smtp.SmtpRequest;
|
||||
import io.netty.handler.codec.smtp.SmtpResponse;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.FutureCompletionStage;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* An example smtp client handler
|
||||
*/
|
||||
public class SmtpClientHandler extends SimpleChannelInboundHandler<SmtpResponse> {
|
||||
|
||||
private ChannelHandlerContext ctx;
|
||||
private final AtomicReference<Promise<SmtpResponse>> responseFuture = new AtomicReference<>();
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void messageReceived(ChannelHandlerContext ctx, SmtpResponse response) throws Exception {
|
||||
Promise<SmtpResponse> promise = responseFuture.get();
|
||||
if (promise == null) {
|
||||
throw new RuntimeException("Unexpected response received: " + response);
|
||||
} else {
|
||||
if (response.code() >= 500) {
|
||||
throw new RuntimeException("receive an error: " + response);
|
||||
}
|
||||
printResponse(response);
|
||||
promise.setSuccess(response);
|
||||
responseFuture.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printResponse(SmtpResponse response) {
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
cause.printStackTrace();
|
||||
responseFuture.get().setFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
public FutureCompletionStage<SmtpResponse> send(SmtpRequest request) {
|
||||
FutureCompletionStage<SmtpResponse> future = createResponseFuture(ctx.executor());
|
||||
ctx.writeAndFlush(request);
|
||||
return future;
|
||||
}
|
||||
|
||||
public FutureCompletionStage<SmtpResponse> sendMailContent(ByteBuf content) {
|
||||
FutureCompletionStage<SmtpResponse> future = createResponseFuture(ctx.executor());
|
||||
ctx.writeAndFlush(new DefaultLastSmtpContent(content));
|
||||
return future;
|
||||
}
|
||||
|
||||
public FutureCompletionStage<SmtpResponse> createResponseFuture(EventExecutor executor) {
|
||||
Promise<SmtpResponse> promise = executor.newPromise();
|
||||
while (!responseFuture.compareAndSet(null, promise)) {
|
||||
Promise<SmtpResponse> previousFuture = responseFuture.get();
|
||||
if (previousFuture != null) {
|
||||
throw new RuntimeException("Still waiting for the past response");
|
||||
}
|
||||
}
|
||||
return promise.asStage();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user