-------------------------------------------------- | |
StubFtpServer Getting Started | |
-------------------------------------------------- | |
StubFtpServer - Getting Started | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
<<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by | |
implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR, | |
DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes, | |
allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can | |
also be interrogated to verify command invocation data such as command parameters and timestamps. | |
<<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured | |
programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container. | |
Here is how to start the <<StubFtpServer>> with the default configuration. This will return | |
success reply codes, and return empty data (for retrieved files, directory listings, etc.). | |
+------------------------------------------------------------------------------ | |
StubFtpServer stubFtpServer = new StubFtpServer(); | |
stubFtpServer.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 will need to use a different server control port. Use the | |
<<<StubFtpServer.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. | |
* CommandHandlers | |
~~~~~~~~~~~~~~~~~ | |
<CommandHandler>s are the heart of the <<StubFtpServer>>. | |
<<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server | |
command. See the list of <CommandHandler> classes associated with FTP server commands in | |
{{{./stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}. | |
You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the | |
<<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command | |
name. For example: | |
+------------------------------------------------------------------------------ | |
PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD"); | |
+------------------------------------------------------------------------------ | |
You can replace the existing <CommandHandler> defined for an FTP server command by calling the | |
<<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing | |
in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the | |
<<<CommandHandler>>> instance. For example: | |
+------------------------------------------------------------------------------ | |
PwdCommandHandler pwdCommandHandler = new PwdCommandHandler(); | |
pwdCommandHandler.setDirectory("some/dir"); | |
stubFtpServer.setCommandHandler("PWD", pwdCommandHandler); | |
+------------------------------------------------------------------------------ | |
** Generic CommandHandlers | |
~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
<<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace | |
the default command handler for an FTP command. See the Javadoc for more information. | |
* <<StaticReplyCommadHandler>> | |
<<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply | |
code and text. This can be a useful replacement for a default <CommandHandler> if you want a | |
certain FTP command to always send back an error reply code. | |
* <<SimpleCompositeCommandHandler>> | |
<<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal | |
ordered list of <CommandHandler>s to which it delegates. Starting with the first | |
<CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to) | |
the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list. | |
** Configuring CommandHandler for a New (Unsupported) Command | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
If you want to add support for a command that is not provided out of the box by <<StubFtpServer>>, | |
you can create a <CommandHandler> instance and set it within the <<StubFtpServer>> using the | |
<<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method in the | |
same way that you replace an existing <CommandHandler> (see above). The following example uses | |
the <<<StaticReplyCommandHandler>>> to add support for the FEAT command. | |
+------------------------------------------------------------------------------ | |
final String FEAT_TEXT = "Extensions supported:\n" + | |
"MLST size*;create;modify*;perm;media-type\n" + | |
"SIZE\n" + | |
"COMPRESSION\n" + | |
"END"; | |
StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT); | |
stubFtpServer.setCommandHandler("FEAT", featCommandHandler); | |
+------------------------------------------------------------------------------ | |
** 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.stub.command.AbstractStubCommandHandler>>> or | |
<<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for | |
more information. | |
* Retrieving Command Invocation Data | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
Each predefined <<StubFtpServer>> <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one | |
for each time the <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>> | |
that triggered the invocation (containing the command name and parameters), as well as the invocation | |
timestamp and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional | |
<CommandHandler>-specific data. See the Javadoc for more information. | |
You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the | |
<<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired | |
invocation. You can get the number of invocations for a <CommandHandler> by calling | |
<<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates | |
retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>. | |
* {Example} Test Using StubFtpServer | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
This section includes a simplified example of FTP client code to be tested, and a JUnit | |
test for it that uses <<StubFtpServer>>. | |
** 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 { | |
private String server; | |
public String readFile(String filename) throws SocketException, IOException { | |
FTPClient ftpClient = new FTPClient(); | |
ftpClient.connect(server); | |
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; | |
} | |
// Other methods ... | |
} | |
+------------------------------------------------------------------------------ | |
** JUnit Test For FTP Client Code Using StubFtpServer | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use | |
<<StubFtpServer>>. The test illustrates replacing the default <CommandHandler> with | |
a customized handler. | |
+------------------------------------------------------------------------------ | |
import java.io.IOException; | |
import org.mockftpserver.core.command.InvocationRecord; | |
import org.mockftpserver.stub.StubFtpServer; | |
import org.mockftpserver.stub.command.RetrCommandHandler; | |
import org.mockftpserver.test.AbstractTest; | |
public class RemoteFileTest extends AbstractTest { | |
private static final String FILENAME = "dir/sample.txt"; | |
private RemoteFile remoteFile; | |
private StubFtpServer stubFtpServer; | |
public void testReadFile() throws Exception { | |
final String CONTENTS = "abcdef 1234567890"; | |
// Replace the default RETR CommandHandler; customize returned file contents | |
RetrCommandHandler retrCommandHandler = new RetrCommandHandler(); | |
retrCommandHandler.setFileContents(CONTENTS); | |
stubFtpServer.setCommandHandler("RETR", retrCommandHandler); | |
stubFtpServer.start(); | |
String contents = remoteFile.readFile(FILENAME); | |
// Verify returned file contents | |
assertEquals("contents", CONTENTS, contents); | |
// Verify the submitted filename | |
InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0); | |
String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY); | |
assertEquals("filename", FILENAME, filename); | |
} | |
/** | |
* Test the readFile() method when the FTP transfer fails (returns a non-success reply code) | |
*/ | |
public void testReadFileThrowsException() { | |
// Replace the default RETR CommandHandler; return failure reply code | |
RetrCommandHandler retrCommandHandler = new RetrCommandHandler(); | |
retrCommandHandler.setFinalReplyCode(550); | |
stubFtpServer.setCommandHandler("RETR", retrCommandHandler); | |
stubFtpServer.start(); | |
try { | |
remoteFile.readFile(FILENAME); | |
fail("Expected IOException"); | |
} | |
catch (IOException expected) { | |
// Expected this | |
} | |
} | |
protected void setUp() throws Exception { | |
super.setUp(); | |
remoteFile = new RemoteFile(); | |
remoteFile.setServer("localhost"); | |
stubFtpServer = new StubFtpServer(); | |
} | |
protected void tearDown() throws Exception { | |
super.tearDown(); | |
stubFtpServer.stop(); | |
} | |
} | |
+------------------------------------------------------------------------------ | |
Things to note about the above test: | |
* The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started | |
there because it must be configured differently for each test. The <<<StubFtpServer>>> instance | |
is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails. | |
* Spring Configuration | |
~~~~~~~~~~~~~~~~~~~~~~ | |
You can easily configure a <<StubFtpServer>> instance in the | |
{{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring> | |
configuration file. | |
+------------------------------------------------------------------------------ | |
<?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="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer"> | |
<property name="commandHandlers"> | |
<map> | |
<entry key="LIST"> | |
<bean class="org.mockftpserver.stub.command.ListCommandHandler"> | |
<property name="directoryListing"> | |
<value> | |
11-09-01 12:30PM 406348 File2350.log | |
11-01-01 1:30PM <DIR> 0 archive | |
</value> | |
</property> | |
</bean> | |
</entry> | |
<entry key="PWD"> | |
<bean class="org.mockftpserver.stub.command.PwdCommandHandler"> | |
<property name="directory" value="foo/bar" /> | |
</bean> | |
</entry> | |
<entry key="DELE"> | |
<bean class="org.mockftpserver.stub.command.DeleCommandHandler"> | |
<property name="replyCode" value="450" /> | |
</bean> | |
</entry> | |
<entry key="RETR"> | |
<bean class="org.mockftpserver.stub.command.RetrCommandHandler"> | |
<property name="fileContents" | |
value="Sample file contents line 1 Line 2 Line 3"/> | |
</bean> | |
</entry> | |
</map> | |
</property> | |
</bean> | |
</beans> | |
+------------------------------------------------------------------------------ | |
This example overrides the default handlers for the following FTP commands: | |
* LIST - replies with a predefined directory listing | |
* PWD - replies with a predefined directory pathname | |
* DELE - replies with an error reply code (450) | |
* RETR - replies with predefined contents for a retrieved file | |
[] | |
And here is the Java code to load the above <Spring> configuration file and start the | |
configured <<StubFtpServer>>. | |
+------------------------------------------------------------------------------ | |
ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml"); | |
stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer"); | |
stubFtpServer.start(); | |
+------------------------------------------------------------------------------ | |
* 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 | |
<<<StubFtpServer.setReplyTextBaseName(String)>>> method. |