maven依赖
<dependency>
<groupId>org.apache.ftpserver</groupId>
<artifactId>ftpserver-core</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.ftpserver</groupId>
<artifactId>ftplet-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.16</version>
</dependency>
java bean 配置结构
核心FtpServerFactory
——需要addListener(“default”, CustomListenerFactory.createListener) 可以定义被动模式的端口范围、外网地址
——需要setFtplets( new HashMap() { “ftpService”: ? extends DefaultFtplet})
class DefaultFtplet {
.... 一些onXXStart | End 方法
public FtpletResult onUploadStart(FtpSession session, FtpRequest request)
throws FtpException, IOException {}
public FtpletResult onUploadEnd(FtpSession session, FtpRequest request)
throws FtpException, IOException {}
——需要setUserManager(? extends AbstractUserManager)
class AbstractUserManager {
...
User getUserByName(String username) throws FtpException;
返回一个BaseUser实例即可,
需要对其设置setHomeDirectory,setAuthorities(new WritePermission() | new ConcurrentLoginPermission(9999,9999)),setUserId、Name、Password
最后调用createServer获得FtpServer,再调用 server.start()开启服务。
总结:创建FtpServer的3要素——监听器(监听器工厂创建)、登录逻辑(UserManager)、事件监听器(Ftplet)
重要源码
底层用mina进行通信
public class FtpHandlerAdapter extends IoHandlerAdapter {
...
public void messageReceived(IoSession session, Object message)
throws Exception {
FtpIoSession ftpSession = new FtpIoSession(session, context);
FtpRequest request = new DefaultFtpRequest(message.toString());
ftpHandler.messageReceived(ftpSession, request);
}
public class DefaultFtpHandler implements FtpHandler {
...
public void messageReceived(final FtpIoSession session,
final FtpRequest request) throws Exception {
try {
session.updateLastAccessTime();
//获取请求中的命令信息
String commandName = request.getCommand();
CommandFactory commandFactory = context.getCommandFactory();
Command command = commandFactory.getCommand(commandName);
FtpletContainer ftplets = context.getFtpletContainer();
//使用我们自定义的ftplet进行预处理判断命令类型
//有DEFAULT、NO_FTPLET、SKIP、DISCONNECT
FtpletResult ftpletRet = ftplets.beforeCommand(session.getFtpletSession(),
request);
//判断预处理结果
if (ftpletRet == FtpletResult.DISCONNECT) {
session.close(false).awaitUninterruptibly(10000);
return;
}
else if (ftpletRet != FtpletResult.SKIP) {
synchronized (session) {
//真正的命令处理
command.execute(session, context, request);
}
ftpletRet = ftplets.afterCommand(
session.getFtpletSession(), request, session
.getLastReply());
}
代码看起来很多总结起来就是——实现了mina的IoHandlerAdapter,处理消息接收的逻辑是调用FtpHandler间接调用Ftplet逻辑。
真正的命令实现
命令执行是这行
command.execute(session, context, request);
它的定义
void execute(FtpIoSession session, FtpServerContext context,
FtpRequest request) throws IOException, FtpException;
查看实现类
发现是FTP协议中的命令,
选择协议中网络通信的第一条命令USER进去看一看——
public class USER extends AbstractCommand {
public void execute(final FtpIoSession session,
final FtpServerContext context, final FtpRequest request)
throws IOException, FtpException {
...
// already logged-in
User user = session.getUser();//从Mina的session中获取用户信息,判断是否登录
boolean anonymous = userName.equals("anonymous");
//看是否开启了匿名登录
// anonymous login limit check
int currAnonLogin = stat.getCurrentAnonymousLoginNumber();
int maxAnonLogin = context.getConnectionConfig()
.getMaxAnonymousLogins();
// login limit check
int currLogin = stat.getCurrentLoginNumber();
int maxLogin = context.getConnectionConfig().getMaxLogins();
//重头戏 运行我们自定义的用户登录逻辑
User configUser = context.getUserManager().getUserByName(userName);
if (configUser != null) {
ConcurrentLoginRequest loginRequest = new ConcurrentLoginRequest(
stat.getCurrentUserLoginNumber(configUser) + 1,
stat.getCurrentUserLoginNumber(configUser, address) + 1);
if (configUser.authorize(loginRequest) == null) {
因为没获取到授权,属于登录失败逻辑
return;
}
success = true;
session.setUserArgument(userName);
session.write(LocalizedFtpReply.translate(session, request, context,
FtpReply.REPLY_331_USER_NAME_OKAY_NEED_PASSWORD,
"USER", userName));
/**
* 331 User name okay, need password.
*/
public static final int REPLY_331_USER_NAME_OKAY_NEED_PASSWORD = 331;
}
命令执行的钩子
在DefaltFtplet(我们做实现通常要继承的那个)逻辑中,
可以看到,实际在命令执行前后会执行很多onXXStart、onXXEnd,这是我们可以做业务逻辑的地方,也就是扩展点。