blob: c342978057f8f6deef5504efcfd3cfd63d5770b5 [file] [log] [blame]
--------------------------------------------------
FakeFtpServer Getting Started
--------------------------------------------------
FakeFtpServer - Getting Started
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<<FakeFtpServer>> is a "fake" implementation of an FTP server. It provides a high-level abstraction for
an FTP Server and is suitable for most testing and simulation scenarios. You define a virtual filesystem
(internal, in-memory) containing an arbitrary set of files and directories. These files and directories can
(optionally) have associated access permissions. You also configure a set of one or more user accounts that
control which users can login to the FTP server, and their home (default) directories. The user account is
also used when assigning file and directory ownership for new files.
<<FakeFtpServer>> processes FTP client requests and responds with reply codes and reply messages
consistent with its configured file system and user accounts, including file and directory permissions,
if they have been configured.
See the {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page for more information on
which features and scenarios are supported.
In general the steps for setting up and starting the <<<FakeFtpServer>>> are:
* Create a new <<<FakeFtpServer>>> instance, and optionally set the server control port (use a value of 0
to automatically choose a free port number).
* Create and configure a <<<FileSystem>>>, and attach to the <<<FakeFtpServer>>> instance.
* Create and configure one or more <<<UserAccount>>> objects and attach to the <<<FakeFtpServer>>> instance.
[]
Here is an example showing configuration and starting of an <<FakeFtpServer>> with a single user
account and a (simulated) Windows file system, defining a directory containing two files.
+------------------------------------------------------------------------------
FakeFtpServer fakeFtpServer = new FakeFtpServer();
fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data"));
FileSystem fileSystem = new WindowsFakeFileSystem();
fileSystem.add(new DirectoryEntry("c:\\data"));
fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
fileSystem.add(new FileEntry("c:\\data\\run.exe"));
fakeFtpServer.setFileSystem(fileSystem);
fakeFtpServer.start();
+------------------------------------------------------------------------------
If you are running on a system where the default port (21) is already in use or cannot be bound
from a user process (such as Unix), you probably need to use a different server control port. Use the
<<<FakeFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port
number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call
<<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port
number being used. Or, you can pass in a specific port number, such as 9187.
<<FakeFtpServer>> can be fully configured programmatically or within the
{{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container.
The {{{#Example}Example Test Using FakeFtpServer}} below illustrates programmatic configuration of
<<<FakeFtpServer>>>. Alternatively, the {{{#Spring}Configuration}} section later on illustrates how to use
the <Spring Framework> to configure a <<<FakeFtpServer>>> instance.
* {Example} Test Using FakeFtpServer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section includes a simplified example of FTP client code to be tested, and a JUnit
test for it that programmatically configures and uses <<FakeFtpServer>>.
** FTP Client Code
~~~~~~~~~~~~~~~~~~
The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote
ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the
{{{http://commons.apache.org/net/}Apache Commons Net}} framework.
+------------------------------------------------------------------------------
public class RemoteFile {
public static final String USERNAME = "user";
public static final String PASSWORD = "password";
private String server;
private int port;
public String readFile(String filename) throws IOException {
FTPClient ftpClient = new FTPClient();
ftpClient.connect(server, port);
ftpClient.login(USERNAME, PASSWORD);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
boolean success = ftpClient.retrieveFile(filename, outputStream);
ftpClient.disconnect();
if (!success) {
throw new IOException("Retrieve file failed: " + filename);
}
return outputStream.toString();
}
public void setServer(String server) {
this.server = server;
}
public void setPort(int port) {
this.port = port;
}
// Other methods ...
}
+------------------------------------------------------------------------------
** JUnit Test For FTP Client Code Using FakeFtpServer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use
<<FakeFtpServer>>.
+------------------------------------------------------------------------------
import org.mockftpserver.fake.filesystem.FileEntry;
import org.mockftpserver.fake.filesystem.FileSystem;
import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
import org.mockftpserver.fake.FakeFtpServer;
import org.mockftpserver.fake.UserAccount;
import org.mockftpserver.stub.example.RemoteFile;
import org.mockftpserver.test.AbstractTest;
import java.io.IOException;
import java.util.List;
public class RemoteFileTest extends AbstractTest {
private static final String HOME_DIR = "/";
private static final String FILE = "/dir/sample.txt";
private static final String CONTENTS = "abcdef 1234567890";
private RemoteFile remoteFile;
private FakeFtpServer fakeFtpServer;
public void testReadFile() throws Exception {
String contents = remoteFile.readFile(FILE);
assertEquals("contents", CONTENTS, contents);
}
public void testReadFileThrowsException() {
try {
remoteFile.readFile("NoSuchFile.txt");
fail("Expected IOException");
}
catch (IOException expected) {
// Expected this
}
}
protected void setUp() throws Exception {
super.setUp();
fakeFtpServer = new FakeFtpServer();
fakeFtpServer.setServerControlPort(0); // use any free port
FileSystem fileSystem = new UnixFakeFileSystem();
fileSystem.add(new FileEntry(FILE, CONTENTS));
fakeFtpServer.setFileSystem(fileSystem);
UserAccount userAccount = new UserAccount(RemoteFile.USERNAME, RemoteFile.PASSWORD, HOME_DIR);
fakeFtpServer.addUserAccount(userAccount);
fakeFtpServer.start();
int port = fakeFtpServer.getServerControlPort();
remoteFile = new RemoteFile();
remoteFile.setServer("localhost");
remoteFile.setPort(port);
}
protected void tearDown() throws Exception {
super.tearDown();
fakeFtpServer.stop();
}
}
+------------------------------------------------------------------------------
Things to note about the above test:
* The <<<FakeFtpServer>>> instance is created and started in the <<<setUp()>>> method and
stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.
* The server control port is set to 0 using <<<fakeFtpServer.setServerControlPort(PORT)>>>.
This means it will dynamically choose a free port. This is necessary if you are running on a
system where the default port (21) is already in use or cannot be bound from a user process (such as Unix).
* The <<<UnixFakeFileSystem>>> filesystem is configured and attached to the <<<FakeFtpServer>>> instance
in the <<<setUp()>>> method. That includes creating a predefined <<<"/dir/sample.txt">>> file with the
specified file contents. The <<<UnixFakeFileSystem>>> has a <<<createParentDirectoriesAutomatically>>>
attribute, which defaults to <<<true>>>, meaning that parent directories will be created automatically,
as necessary. In this case, that means that the <<<"/">>> and <<<"/dir">>> parent directories will be created,
even though not explicitly specified.
* A single <<<UserAccount>>> with the specified username, password and home directory is configured and
attached to the <<<FakeFtpServer>>> instance in the <<<setUp()>>> method. That configured user ("user")
is the only one that will be able to sucessfully log in to the <<<FakeFtpServer>>>.
* {Spring} Configuration
~~~~~~~~~~~~~~~~~~~~~~~~
You can easily configure a <<<FakeFtpServer>>> instance in the
{{{http://www.springframework.org/}Spring Framework}} or another, similar dependency-injection container.
** Simple Spring Configuration Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following example shows a <Spring> configuration file for a simple <<<FakeFtpServer>>> instance.
+------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
<property name="serverControlPort" value="9981"/>
<property name="systemName" value="UNIX"/>
<property name="userAccounts">
<list>
<bean class="org.mockftpserver.fake.UserAccount">
<property name="username" value="joe"/>
<property name="password" value="password"/>
<property name="homeDirectory" value="/"/>
</bean>
</list>
</property>
<property name="fileSystem">
<bean class="org.mockftpserver.fake.filesystem.UnixFakeFileSystem">
<property name="createParentDirectoriesAutomatically" value="false"/>
<property name="entries">
<list>
<bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
<property name="path" value="/"/>
</bean>
<bean class="org.mockftpserver.fake.filesystem.FileEntry">
<property name="path" value="/File.txt"/>
<property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
</bean>
</list>
</property>
</bean>
</property>
</bean>
</beans>
+------------------------------------------------------------------------------
Things to note about the above example:
* The <<<FakeFtpServer>>> instance has a single user account for username "joe", password "password"
and home (default) directory of "/".
* A <<<UnixFakeFileSystem>>> instance is configured with a predefined directory of "/" and a
"/File.txt" file with the specified contents.
[]
And here is the Java code to load the above <Spring> configuration file and start the
configured <<FakeFtpServer>>.
+------------------------------------------------------------------------------
ApplicationContext context = new ClassPathXmlApplicationContext("fakeftpserver-beans.xml");
FakeFtpServer = (FakeFtpServer) context.getBean("FakeFtpServer");
FakeFtpServer.start();
+------------------------------------------------------------------------------
** Spring Configuration Example With File and Directory Permissions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following example shows a <Spring> configuration file for a <<<FakeFtpServer>>> instance that
also configures file and directory permissions. This will enable the <<<FakeFtpServer>>> to reply
with proper error codes when the logged in user does not have the required permissions to access
directories or files.
+------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
<property name="serverControlPort" value="9981"/>
<property name="userAccounts">
<list>
<bean class="org.mockftpserver.fake.UserAccount">
<property name="username" value="joe"/>
<property name="password" value="password"/>
<property name="homeDirectory" value="c:\"/>
</bean>
</list>
</property>
<property name="fileSystem">
<bean class="org.mockftpserver.fake.filesystem.WindowsFakeFileSystem">
<property name="createParentDirectoriesAutomatically" value="false"/>
<property name="entries">
<list>
<bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
<property name="path" value="c:\"/>
<property name="permissionsFromString" value="rwxrwxrwx"/>
<property name="owner" value="joe"/>
<property name="group" value="users"/>
</bean>
<bean class="org.mockftpserver.fake.filesystem.FileEntry">
<property name="path" value="c:\File1.txt"/>
<property name="contents" value="1234567890"/>
<property name="permissionsFromString" value="rwxrwxrwx"/>
<property name="owner" value="peter"/>
<property name="group" value="users"/>
</bean>
<bean class="org.mockftpserver.fake.filesystem.FileEntry">
<property name="path" value="c:\File2.txt"/>
<property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
<property name="permissions">
<bean class="org.mockftpserver.fake.filesystem.Permissions">
<constructor-arg value="rwx------"/>
</bean>
</property>
<property name="owner" value="peter"/>
<property name="group" value="users"/>
</bean>
</list>
</property>
</bean>
</property>
</bean>
</beans>
+------------------------------------------------------------------------------
Things to note about the above example:
* The <<<FakeFtpServer>>> instance is configured with a <<<WindowsFakeFileSystem>>> and a "c:\" root
directory containing two files. Permissions and owner/group are specified for that directory, as well
as the two predefined files contained within it.
* The permissions for "File1.txt" ("rwxrwxrwx") are specified using the "permissionsFromString" shortcut
method, while the permissions for "File2.txt" ("rwx------") are specified using the "permissions" setter,
which takes an instance of the <<<Permissions>>> class. Either method is fine.
[]
* Configuring Custom CommandHandlers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<<FakeFtpServer>> is intentionally designed to keep the lower-level details of FTP server implementation
hidden from the user. In most cases, you can simply define the files and directories in the file
system, configure one or more login users, and then fire up the server, expecting it to behave like
a <real> FTP server.
There are some cases, however, where you might want to further customize the internal behavior of the
server. Such cases might include:
* You want to have a particular FTP server command return a predetermined error reply
* You want to add support for a command that is not provided out of the box by <<FakeFtpServer>>
Note that if you need the FTP server to reply with entirely predetermined (canned) responses, then
you may want to consider using <<StubFtpServer>> instead.
** Using a StaticReplyCommandHandler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can use one of the <CommandHandler> classes defined within the <<<org.mockftpserver.core.command>>>
package to configure a custom <CommandHandler>. The following example uses the <<<StaticReplyCommandHandler>>>
from that package to add support for the FEAT command. Note that in this case, we are setting the
<CommandHandler> for a new command (i.e., one that is not supported out of the box by <<FakeFtpServer>>).
We could just as easily set the <CommandHandler> for an existing command, overriding the default <CommandHandler>.
+------------------------------------------------------------------------------
import org.mockftpserver.core.command.StaticReplyCommandHandler
FakeFtpServer ftpServer = new FakeFtpServer()
// ... set up files, directories and user accounts as usual
StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
ftpServer.setCommandHandler("FEAT", featCommandHandler);
// ...
ftpServer.start()
+------------------------------------------------------------------------------
** Using a Stub CommandHandler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can also use a <<StubFtpServer>> <CommandHandler> -- i.e., one defined within the
<<<org.mockftpserver.stub.command>>> package. The following example uses the <stub> version of the
<<<CwdCommandHandler>>> from that package.
+------------------------------------------------------------------------------
import org.mockftpserver.stub.command.CwdCommandHandler
FakeFtpServer ftpServer = new FakeFtpServer()
// ... set up files, directories and user accounts as usual
final int REPLY_CODE = 502;
CwdCommandHandler cwdCommandHandler = new CwdCommandHandler();
cwdCommandHandler.setReplyCode(REPLY_CODE);
ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
// ...
ftpServer.start()
+------------------------------------------------------------------------------
** Creating Your Own Custom CommandHandler Class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create
your own custom <CommandHandler> class. The only requirement is that it implement the
<<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from
inheriting from one of the existing abstract <CommandHandler> superclasses, such as
<<<org.mockftpserver.core.command.AbstractStaticReplyCommandHandler>>> or
<<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for
more information.
* FTP Command Reply Text ResourceBundle
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default text asociated with each FTP command reply code is contained within the
"ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
completely replace the ResourceBundle file by calling the calling the
<<<FakeFtpServer.setReplyTextBaseName(String)>>> method.
* SLF4J Configuration Required to See Log Output
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note that <<FakeFtpServer>> uses {{{http://www.slf4j.org/}SLF4J}} for logging. If you want to
see the logging output, then you must configure <<SLF4J>>. (If no binding is found on the class
path, then <<SLF4J>> will default to a no-operation implementation.)
See the {{{http://www.slf4j.org/manual.html}SLF4J User Manual}} for more information.