Detailed tutorial of integrating apache ftpserver with springboot (this is enough)

Posted by jmdavis on Tue, 14 Jan 2020 09:51:53 +0100

Original is not easy, if you need to reprint, please indicate the source https://www.cnblogs.com/baixianlong/p/12192425.html Otherwise, the legal responsibility will be investigated!!!

1, About Apache ftpserver

Apache FTP server is a 100% pure Java FTP server. It is designed as a complete and portable FTP server engine solution based on the currently available open protocol. FtpServer can run independently as a Windows service or Unix / Linux daemons, or it can be embedded in Java applications. We also provide support for integration within Spring applications and provide our distribution in the form of OSGi bundles. The default network support is based on Apache MINA, a high-performance asynchronous IO library. With MINA, FtpServer can be extended to a large number of concurrent users.

2, Apache ftpserver related features

  • 100% pure Java, free open source recoverable FTP server
  • Multi platform support and multi thread design.
  • User virtual directory, write permission, idle timeout and upload / download bandwidth limit support.
  • Anonymous login support.
  • Both upload and download files are recoverable.
  • Handle ASCII and binary data transfer.
  • Supports IP restriction to disable IP.
  • data base And files can be used to store user data.
  • All FTP messages are customizable.
  • Implicit / explicit SSL / TLS support.
  • MDTM support - your user can change the date and time stamp of the file.
  • Mode Z supports faster data upload / download.
  • Can easily add custom user manager, IP limiter, recorder.
  • You can add user event notifications (ftlets).

3, Simple deployment and use of Apache ftpserver (based on windows, linux is the same)

users.properties file configuration

    For example, configure a bxl User:
    #Password configuration new user
    ftpserver.user.bxl.userpassword=123456
    #Home directory, where you can customize your own home directory
    ftpserver.user.bxl.homedirectory=./res/bxl-home
    #Current user available
    ftpserver.user.bxl.enableflag=true
    #Have upload permission
    ftpserver.user.bxl.writepermission=true
    #The maximum number of login users is 20
    ftpserver.user.bxl.maxloginnumber=20
    #The same number of IP login users is 2
    ftpserver.user.bxl.maxloginperip=2
    #300 seconds idle
    ftpserver.user.bxl.idletime=300
    #Upload rate limited to 480000 bytes per second
    ftpserver.user.bxl.uploadrate=48000000
    #Download rate limited to 480000 bytes per second
    ftpserver.user.bxl.downloadrate=48000000

ftpd-typical.xml file configuration

    <server xmlns="http://mina.apache.org/ftpserver/spring/v1"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd" id="myServer">
        <listeners>
            <nio-listener name="default" port="2121">
                <ssl>
                    <keystore file="./res/ftpserver.jks" password="password" />
                </ssl>
                <!--Be careful:If you want to support Internet connection, you need to use passive mode passive,Active mode on by default-->
                <data-connection idle-timeout="60">
                    <active enabled="true" ip-check="true" />
                    <!-- <passive ports="2000-2222" address="0.0.0.0" external-address="xxx.xxx.xxx.xxx" /> -->
                </data-connection>
                <!--Add to ip Blacklist-->
                <blacklist>127.0.0.1</blacklist>
            </nio-listener>
        </listeners>
        
        <!--Add here encrypt-passwords="clear",Remove password encryption-->
        <file-user-manager file="./res/conf/users.properties" encrypt-passwords="clear" />
    </server>
  • 3. Launch and access
    • First, start the service, open cmd and cd to bin path to execute. \ ftpd.bat res/conf/ftpd-typical.xml. See the following status to indicate that the startup is successful

    • Test access, open the browser and input: ftp://localhost:2121 /, you will see your file directory. If anonymous users are not configured, you will be asked to input the user name and password, which is exactly what you configured in user.properties

4, Spring boot integrates Apache ftpserver (key)

Mode 1: independently deploy ftpserver service

This method is relatively simple, as long as the service is deployed, and then the relevant operations are completed through ftpclean, which is the same as jedis access redis There's nothing to say about service. Pay attention to the access mode of ftpserver. If you want to support external network connection, you need to use passive mode.

Mode 2: embed the ftpserver service into the springboot service

This method needs to be integrated with spring boot, which is relatively complex. But in this way, ftpserver will be opened or destroyed as spring boot service is started or shut down. Which way to use depends on your business needs.

To briefly describe my implementation, ftpserver supports two ways: configuration file and db to save account information and other related configurations. If our business system needs to get through user information and ftp account information, as well as related business statistics, such as the time and number of files uploaded by each person in the system, then use database to save ft P account information is more convenient and flexible. I choose to use mysql here.

Start integration

  • 1. Project add dependency

    //These are only the dependencies related to apache ftpserver. You can add the dependencies of the springboot project
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25 </version>
    </dependency>
    <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>   
  • 2. Database tables are used to save related account information (you can add several manually for testing). For specific field meanings, please refer to the users.properties file configuration (you can imagine that in the future, every user registered in our system can add an FTP user information for it, which is used to specify and save the user's upload data, etc.)

    CREATE TABLE FTP_USER (      
       userid VARCHAR(64) NOT NULL PRIMARY KEY,       
       userpassword VARCHAR(64),      
       homedirectory VARCHAR(128) NOT NULL,             
       enableflag BOOLEAN DEFAULT TRUE,    
       writepermission BOOLEAN DEFAULT FALSE,       
       idletime INT DEFAULT 0,             
       uploadrate INT DEFAULT 0,             
       downloadrate INT DEFAULT 0,
       maxloginnumber INT DEFAULT 0,
       maxloginperip INT DEFAULT 0
    );
  • 3. Configure ftpserver and provide init(), start(), stop() methods of ftpserver

    import com.mysql.cj.jdbc.MysqlDataSource;
    import com.talkingdata.tds.ftpserver.plets.MyFtpPlet;
    import org.apache.commons.io.IOUtils;
    import org.apache.ftpserver.DataConnectionConfigurationFactory;
    import org.apache.ftpserver.FtpServer;
    import org.apache.ftpserver.FtpServerFactory;
    import org.apache.ftpserver.ftplet.FtpException;
    import org.apache.ftpserver.ftplet.Ftplet;
    import org.apache.ftpserver.listener.Listener;
    import org.apache.ftpserver.listener.ListenerFactory;
    import org.apache.ftpserver.ssl.SslConfigurationFactory;
    import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
    import org.apache.ftpserver.usermanager.DbUserManagerFactory;
    import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.stereotype.Component;
    
    
    import javax.sql.DataSource;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Note: the class marked with @ Configuration will be added to the ioc container, and all the @ Bean annotated methods in the class will be dynamically proxied, so the same instance will be returned when calling this method.
     * ftp Service access address:
     *      ftp://localhost:3131/
     */
    @Configuration("MyFtp")
    public class MyFtpServer {
    
        private static final Logger logger = LoggerFactory.getLogger(MyFtpServer.class);
    
        //Spring boot is configured to inject data directly
        @Autowired
        private DataSource dataSource;
        protected FtpServer server;
    
        //We use spring to load @ Configuration to initialize ftp server
        public MyFtpServer(DataSource dataSource) {
            this.dataSource = dataSource;
            initFtp();
            logger.info("Apache ftp server is already instantiation complete!");
        }
    
        /**
         * ftp server init
         * @throws IOException
         */
        public void initFtp() {
            FtpServerFactory serverFactory = new FtpServerFactory();
            ListenerFactory listenerFactory = new ListenerFactory();
            //1. Set service port
            listenerFactory.setPort(3131);
            //2. Set the interface range of passive mode data upload, and the ECS needs to open the corresponding port to the client
            DataConnectionConfigurationFactory dataConnectionConfFactory = new DataConnectionConfigurationFactory();
            dataConnectionConfFactory.setPassivePorts("10000-10500");
            listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration());
            //3. Add SSL security configuration
    //        SslConfigurationFactory ssl = new SslConfigurationFactory();
    //        ssl.setKeystoreFile(new File("src/main/resources/ftpserver.jks"));
    //        ssl.setKeystorePassword("password");
            //ssl.setSslProtocol("SSL");
            // set the SSL configuration for the listener
    //        listenerFactory.setSslConfiguration(ssl.createSslConfiguration());
    //        listenerFactory.setImplicitSsl(true);
            //4. Replace the default listener
            Listener listener = listenerFactory.createListener();
            serverFactory.addListener("default", listener);
            //5. Configure custom user events
            Map<String, Ftplet> ftpLets = new HashMap();
            ftpLets.put("ftpService", new MyFtpPlet());
            serverFactory.setFtplets(ftpLets);
            //6. Read user's configuration information
            //Note: the configuration file is located in the resources directory. If the project is published as a jar package using the built-in container, FTPServer cannot directly read the configuration file in the jar package.
            //Solution: copy the file to the specified directory (specified in this article to the root directory) before FTPServer can read it.
    //        PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
    //        String tempPath = System.getProperty("java.io.tmpdir") + System.currentTimeMillis() + ".properties";
    //        File tempConfig = new File(tempPath);
    //        ClassPathResource resource = new ClassPathResource("users.properties");
    //        IOUtils.copy(resource.getInputStream(), new FileOutputStream(tempConfig));
    //        userManagerFactory.setFile(tempConfig);
    //        userManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor()); / / password in clear text
    //        serverFactory.setUserManager(userManagerFactory.createUserManager());
            //6.2. Store user instance based on Database
            DbUserManagerFactory dbUserManagerFactory = new DbUserManagerFactory();
            //todo....
            dbUserManagerFactory.setDataSource(dataSource);
            dbUserManagerFactory.setAdminName("admin");
            dbUserManagerFactory.setSqlUserAdmin("SELECT userid FROM FTP_USER WHERE userid='{userid}' AND userid='admin'");
            dbUserManagerFactory.setSqlUserInsert("INSERT INTO FTP_USER (userid, userpassword, homedirectory, " +
                    "enableflag, writepermission, idletime, uploadrate, downloadrate) VALUES " +
                    "('{userid}', '{userpassword}', '{homedirectory}', {enableflag}, " +
                    "{writepermission}, {idletime}, uploadrate}, {downloadrate})");
            dbUserManagerFactory.setSqlUserDelete("DELETE FROM FTP_USER WHERE userid = '{userid}'");
            dbUserManagerFactory.setSqlUserUpdate("UPDATE FTP_USER SET userpassword='{userpassword}',homedirectory='{homedirectory}',enableflag={enableflag},writepermission={writepermission},idletime={idletime},uploadrate={uploadrate},downloadrate={downloadrate},maxloginnumber={maxloginnumber}, maxloginperip={maxloginperip} WHERE userid='{userid}'");
            dbUserManagerFactory.setSqlUserSelect("SELECT * FROM FTP_USER WHERE userid = '{userid}'");
            dbUserManagerFactory.setSqlUserSelectAll("SELECT userid FROM FTP_USER ORDER BY userid");
            dbUserManagerFactory.setSqlUserAuthenticate("SELECT userid, userpassword FROM FTP_USER WHERE userid='{userid}'");
            dbUserManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor());
            serverFactory.setUserManager(dbUserManagerFactory.createUserManager());
            //7. Instantiate FTP Server
            server = serverFactory.createServer();
        }
    
    
        /**
         * ftp server start
         */
        public void start(){
            try {
                server.start();
                logger.info("Apache Ftp server is starting!");
            }catch(FtpException e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * ftp server stop
         */
        public void stop() {
            server.stop();
            logger.info("Apache Ftp server is stoping!");
        }
    
    }
  • 4. Configure the listener to start ftpserver when the spring container starts and stop ftpserver when the spring container is destroyed

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    
    @WebListener
    public class FtpServerListener implements ServletContextListener {
    
        private static final Logger logger = LoggerFactory.getLogger(MyFtpServer.class);
        private static final String SERVER_NAME="FTP-SERVER";
    
        @Autowired
        private MyFtpServer server;
    
        //Call method stop FTP server when container is closed
        public void contextDestroyed(ServletContextEvent sce) {
    //        WebApplicationContext ctx= WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
    //        MyFtpServer server=(MyFtpServer)ctx.getServletContext().getAttribute(SERVER_NAME);
            server.stop();
            sce.getServletContext().removeAttribute(SERVER_NAME);
            logger.info("Apache Ftp server is stoped!");
        }
    
        //Container initialization calls start ftpServer
        public void contextInitialized(ServletContextEvent sce) {
    //        WebApplicationContext ctx= WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
    //        MyFtpServer server=(MyFtpServer) ctx.getBean("MyFtp");
            sce.getServletContext().setAttribute(SERVER_NAME,server);
            try {
                //The project was loaded when it started
                server.start();
                logger.info("Apache Ftp server is started!");
            } catch (Exception e){
                e.printStackTrace();
                throw new RuntimeException("Apache Ftp server start failed!", e);
            }
        }
    
    }
  • 5, Implement some custom user events by inheriting the DefaultFtplet abstract class (I'll just give you an example here)

    import org.apache.ftpserver.ftplet.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    
    public class MyFtpPlet extends DefaultFtplet {
    
        private static final Logger logger = LoggerFactory.getLogger(MyFtpPlet.class);
    
        @Override
        public FtpletResult onUploadStart(FtpSession session, FtpRequest request)
                throws FtpException, IOException {
            //Get the upload path of the uploaded file
            String path = session.getUser().getHomeDirectory();
            //Get upload users
            String name = session.getUser().getName();
            //Get upload file name
            String filename = request.getArgument();
            logger.info("user:'{}',Upload file to directory:'{}',The file name is:'{}',Status: start upload~", name, path, filename);
            return super.onUploadStart(session, request);
        }
    
    
        @Override
        public FtpletResult onUploadEnd(FtpSession session, FtpRequest request)
                throws FtpException, IOException {
            //Get the upload path of the uploaded file
            String path = session.getUser().getHomeDirectory();
            //Get upload users
            String name = session.getUser().getName();
            //Get upload file name
            String filename = request.getArgument();
            logger.info("user:'{}',Upload file to directory:'{}',The file name is:'{},Status: successful!'", name, path, filename);
            return super.onUploadEnd(session, request);
        }
    
        @Override
        public FtpletResult onDownloadStart(FtpSession session, FtpRequest request) throws FtpException, IOException {
            //todo servies...
            return super.onDownloadStart(session, request);
        }
    
        @Override
        public FtpletResult onDownloadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException {
            //todo servies...
            return super.onDownloadEnd(session, request);
        }
    
    }
  • 6. Configure access to spring boot static resources

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    
    @Configuration
    public class FtpConfig implements WebMvcConfigurer {
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            //Can be judged by os
            String os = System.getProperty("os.name");
            //linux settings
    //        registry.addResourceHandler("/ftp/**").addResourceLocations("file:/home/pic/");
            //windows settings
            //The first method sets the access path prefix, and the second method sets the resource path. You can specify either the project classpath path or other non project paths
            registry.addResourceHandler("/ftp/**").addResourceLocations("file:D:\\apache-ftpserver-1.1.1\\res\\bxl-home\\");
            registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        }
    
    }
  • 7. The above 6 steps have completed the configuration of ftpserver. With the start of springboot project, the ftpserver service will be started. Next, we will paste the util accessed by the client, and you can encapsulate it yourself.

    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPFile;
    import org.apache.commons.net.ftp.FTPReply;
    import org.apache.commons.net.ftp.FTPSClient;
    
    import java.io.*;
    
    public class FtpClientUtil {
    
        // ip address of ftp server
        private static String FTP_ADDRESS = "localhost";
        // Port number
        private static int FTP_PORT = 3131;
        // User name
        private static String FTP_USERNAME = "bxl";
        // Password
        private static String FTP_PASSWORD = "123456";
        // Relative path
        private static String FTP_BASEPATH = "";
    
        public static boolean uploadFile(String remoteFileName, InputStream input) {
            boolean flag = false;
            FTPClient ftp = new FTPClient();
            ftp.setControlEncoding("UTF-8");
            try {
                int reply;
                ftp.connect(FTP_ADDRESS, FTP_PORT);// Connect to FTP server
                ftp.login(FTP_USERNAME, FTP_PASSWORD);// Sign in
                reply = ftp.getReplyCode();
                System.out.println("Sign in ftp The service return status code is:" + reply);
                if (!FTPReply.isPositiveCompletion(reply)) {
                    ftp.disconnect();
                    return flag;
                }
                ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
                //Set to passive mode
                ftp.enterLocalPassiveMode();
                ftp.makeDirectory(FTP_BASEPATH);
                ftp.changeWorkingDirectory(FTP_BASEPATH);
                //originFilePath is the file name of the uploaded file. It is recommended to use the generated unique name. It is better to transcode the Chinese name
                boolean a = ftp.storeFile(remoteFileName, input);
    //            boolean a = ftp.storeFile(new String(remoteFileName.getBytes(),"iso-8859-1"),input);
                System.out.println("The original file name to upload is:" + remoteFileName + ", Upload results:" + a);
                input.close();
                ftp.logout();
                flag = true;
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (ftp.isConnected()) {
                    try {
                        ftp.disconnect();
                    } catch (IOException ioe) {
                    }
                }
            }
            return flag;
        }
    
    //    public static Boolean uploadFile(String remoteFileName, InputStream inputStream, String ftpAddress, int ftpPort,
    //                                     String ftpName, String ftpPassWord, String ftpBasePath) {
    //        FTP_ADDRESS = ftpAddress;
    //        FTP_PORT = ftpPort;
    //        FTP_USERNAME = ftpName;
    //        FTP_PASSWORD = ftpPassWord;
    //        FTP_BASEPATH = ftpBasePath;
    //        uploadFile(remoteFileName,inputStream);
    //        return true;
    //    }
    
        public static boolean deleteFile(String filename) {
            boolean flag = false;
            FTPClient ftpClient = new FTPClient();
            try {
                // Connect to FTP server
                ftpClient.connect(FTP_ADDRESS, FTP_PORT);
                // Log in to FTP server
                ftpClient.login(FTP_USERNAME, FTP_PASSWORD);
                // Verify FTP server login success
                int replyCode = ftpClient.getReplyCode();
                if (!FTPReply.isPositiveCompletion(replyCode)) {
                    return flag;
                }
                // Switch FTP directory
                ftpClient.changeWorkingDirectory(FTP_BASEPATH);
                ftpClient.dele(filename);
                ftpClient.logout();
                flag = true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.logout();
                    } catch (IOException e) {
    
                    }
                }
            }
            return flag;
        }
    
        public static boolean downloadFile(String filename, String localPath) {
            boolean flag = false;
    //        FTPSClient ftpClient = new FTPSClient("TLS", true);
            FTPClient ftpClient = new FTPClient();
            try {
                // Connect to FTP server
                ftpClient.connect(FTP_ADDRESS, FTP_PORT);
                // Log in to FTP server
                ftpClient.login(FTP_USERNAME, FTP_PASSWORD);
                // Verify FTP server login success
                int replyCode = ftpClient.getReplyCode();
                if (!FTPReply.isPositiveCompletion(replyCode)) {
                    return flag;
                }
                // Switch FTP directory
                ftpClient.changeWorkingDirectory(FTP_BASEPATH);
                //This is the demo method. Normally, you should query the fileName in the database
                FTPFile[] ftpFiles = ftpClient.listFiles();
                for (FTPFile file : ftpFiles) {
                    if (filename.equalsIgnoreCase(file.getName())) {
                        File localFile = new File(localPath + "/" + file.getName());
                        OutputStream os = new FileOutputStream(localFile);
                        ftpClient.retrieveFile(file.getName(), os);
                        os.close();
                    }
                }
                ftpClient.logout();
                flag = true;
                System.out.println("File download completed!!!");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.logout();
                    } catch (IOException e) {
    
                    }
                }
            }
            return flag;
        }
    }

Five, summary

So far, all the configurations have been completed, and our business system also plays a role, that is, ftp server. The whole configuration has not joined SSL/TLS security mechanism. If you are interested, you can study it yourself. I annotate that part of my code, just notice that when accessing through the client, you need to use ftp scliet instead of ftp Cliet. Of course, you also need to configure your own ftpserver.jks file, which is java key store. Baidu next how to generate, very simple Oh!

Personal blog address:

cnblogs: https://www.cnblogs.com/baixianlong/

github: https://github.com/xianlongbai

101 original articles published, 5 praised, 3782 visited
Private letter follow

Topics: ftp Apache SSL Java