1 -------------------------------------------------- 2 StubFtpServer Getting Started 3 -------------------------------------------------- 4 5 StubFtpServer - Getting Started 6 7 <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by 8 implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR, 9 DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes, 10 allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can 11 also be interrogated to verify command invocation data such as command parameters and timestamps. 12 13 <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured 14 programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container. 15 16 Here is how to start the <<StubFtpServer>> with the default configuration. This will return 17 success reply codes, and return empty data (for retrieved files, directory listings, etc.). 18 19 +------------------------------------------------------------------------------ 20 StubFtpServer stubFtpServer = new StubFtpServer(); 21 stubFtpServer.start(); 22 +------------------------------------------------------------------------------ 23 24 If you are running on a Unix system, you probably need to use a different server control port, since 25 the default port (21) is likely already in use or cannot be bound from a user process. Use the 26 <<<StubFtpServer.setServerControlPort(int serverControlPort)>>> method to set a different port 27 number, such as 9187. 28 29 * CommandHandlers 30 31 <CommandHandler>s are the heart of the <<StubFtpServer>>. 32 33 <<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server 34 command. See the list of <CommandHandler> classes associated with FTP server commands in 35 {{{stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}. 36 37 You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the 38 <<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command 39 name. For example: 40 41 +------------------------------------------------------------------------------ 42 PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD"); 43 +------------------------------------------------------------------------------ 44 45 You can replace the existing <CommandHandler> defined for an FTP server command by calling the 46 <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing 47 in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the 48 <<<CommandHandler>>> instance. For example: 49 50 +------------------------------------------------------------------------------ 51 PwdCommandHandler pwdCommandHandler = new PwdCommandHandler(); 52 pwdCommandHandler.setDirectory("some/dir"); 53 stubFtpServer.setCommandHandler("PWD", pwdCommandHandler); 54 +------------------------------------------------------------------------------ 55 56 57 ** Generic CommandHandlers 58 59 <<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace 60 the default command handler for an FTP command. See the Javadoc for more information. 61 62 * <<StaticReplyCommadHandler>> 63 64 <<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply 65 code and text. This can be a useful replacement for a default <CommandHandler> if you want a 66 certain FTP command to always send back an error reply code. 67 68 * <<SimpleCompositeCommandHandler>> 69 70 <<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal 71 ordered list of <CommandHandler>s to which it delegates. Starting with the first 72 <CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to) 73 the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list. 74 75 76 * Programmatic Configuration 77 78 You can customize the behavior of the FTP server through programmatic configuration. 79 <<StubFtpServer>> automatically creates a default <CommandHandler> for each supported command. 80 If you want to customize the behavior of the server, you should create and configure a replacement 81 <CommandHandler> for each command to be customized. 82 83 The {{{#Example}Example Test Using Stub Ftp Server}} illustrates replacing the default 84 <CommandHandler> with a customized handler. 85 86 * Spring Configuration 87 88 You can easily configure a <<StubFtpServer>> instance in the 89 {{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring> 90 configuration file. 91 92 +------------------------------------------------------------------------------ 93 <?xml version="1.0" encoding="UTF-8"?> 94 95 <beans xmlns="http://www.springframework.org/schema/beans" 96 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 97 xsi:schemaLocation="http://www.springframework.org/schema/beans 98 http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> 99 100 <bean id="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer"> 101 102 <property name="commandHandlers"> 103 <map> 104 <entry key="LIST"> 105 <bean class="org.mockftpserver.stub.command.ListCommandHandler"> 106 <property name="directoryListing"> 107 <value> 108 11-09-01 12:30PM 406348 File2350.log 109 11-01-01 1:30PM <DIR> 0 archive 110 </value> 111 </property> 112 </bean> 113 </entry> 114 115 <entry key="PWD"> 116 <bean class="org.mockftpserver.stub.command.PwdCommandHandler"> 117 <property name="directory" value="foo/bar" /> 118 </bean> 119 </entry> 120 121 <entry key="DELE"> 122 <bean class="org.mockftpserver.stub.command.DeleCommandHandler"> 123 <property name="replyCode" value="450" /> 124 </bean> 125 </entry> 126 127 <entry key="RETR"> 128 <bean class="org.mockftpserver.stub.command.RetrCommandHandler"> 129 <property name="fileContents" 130 value="Sample file contents line 1 Line 2 Line 3"/> 131 </bean> 132 </entry> 133 134 </map> 135 </property> 136 </bean> 137 138 </beans> 139 +------------------------------------------------------------------------------ 140 141 This example overrides the default handlers for the following FTP commands: 142 143 * LIST - replies with a predefined directory listing 144 145 * PWD - replies with a predefined directory pathname 146 147 * DELE - replies with an error reply code (450) 148 149 * RETR - replies with predefined contents for a retrieved file 150 151 [] 152 153 And here is the Java code to load the above <Spring> configuration file and start the 154 configured <<StubFtpServer>>. 155 156 +------------------------------------------------------------------------------ 157 ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml"); 158 stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer"); 159 stubFtpServer.start(); 160 +------------------------------------------------------------------------------ 161 162 163 * Retrieving Command Invocation Data 164 165 Each <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one for each time the 166 <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>> that triggered 167 the invocation (containing the command name and parameters), as well as the invocation timestamp 168 and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional 169 <CommandHandler>-specific data. See the Javadoc for more information. 170 171 You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the 172 <<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired 173 invocation. You can get the number of invocations for a <CommandHandler> by calling 174 <<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates 175 retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>. 176 177 178 179 * {Example} Test Using StubFtpServer 180 181 This section includes a simplified example of FTP client code to be tested, and a JUnit 182 test for it that uses <<StubFtpServer>>. 183 184 ** FTP Client Code 185 186 The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote 187 ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the 188 {{{http://commons.apache.org/net/}Apache Commons Net}} framework. 189 190 +------------------------------------------------------------------------------ 191 public class RemoteFile { 192 193 private String server; 194 195 public String readFile(String filename) throws SocketException, IOException { 196 197 FTPClient ftpClient = new FTPClient(); 198 ftpClient.connect(server); 199 200 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 201 boolean success = ftpClient.retrieveFile(filename, outputStream); 202 ftpClient.disconnect(); 203 204 if (!success) { 205 throw new IOException("Retrieve file failed: " + filename); 206 } 207 return outputStream.toString(); 208 } 209 210 public void setServer(String server) { 211 this.server = server; 212 } 213 214 // Other methods ... 215 } 216 +------------------------------------------------------------------------------ 217 218 ** JUnit Test For FTP Client Code Using StubFtpServer 219 220 The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use 221 <<StubFtpServer>>. 222 223 +------------------------------------------------------------------------------ 224 import java.io.IOException; 225 import org.mockftpserver.core.command.InvocationRecord; 226 import org.mockftpserver.stub.StubFtpServer; 227 import org.mockftpserver.stub.command.RetrCommandHandler; 228 import org.mockftpserver.test.AbstractTest; 229 230 public class RemoteFileTest extends AbstractTest { 231 232 private static final String FILENAME = "dir/sample.txt"; 233 234 private RemoteFile remoteFile; 235 private StubFtpServer stubFtpServer; 236 237 /** 238 * Test readFile() method 239 */ 240 public void testReadFile() throws Exception { 241 242 final String CONTENTS = "abcdef 1234567890"; 243 244 // Replace the default RETR CommandHandler; customize returned file contents 245 RetrCommandHandler retrCommandHandler = new RetrCommandHandler(); 246 retrCommandHandler.setFileContents(CONTENTS); 247 stubFtpServer.setCommandHandler("RETR", retrCommandHandler); 248 249 stubFtpServer.start(); 250 251 String contents = remoteFile.readFile(FILENAME); 252 253 // Verify returned file contents 254 assertEquals("contents", CONTENTS, contents); 255 256 // Verify the submitted filename 257 InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0); 258 String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY); 259 assertEquals("filename", FILENAME, filename); 260 } 261 262 /** 263 * Test the readFile() method when the FTP transfer fails (returns a non-success reply code) 264 */ 265 public void testReadFileThrowsException() { 266 267 // Replace the default RETR CommandHandler; return failure reply code 268 RetrCommandHandler retrCommandHandler = new RetrCommandHandler(); 269 retrCommandHandler.setFinalReplyCode(550); 270 stubFtpServer.setCommandHandler("RETR", retrCommandHandler); 271 272 stubFtpServer.start(); 273 274 try { 275 remoteFile.readFile(FILENAME); 276 fail("Expected IOException"); 277 } 278 catch (IOException expected) { 279 // Expected this 280 } 281 } 282 283 /** 284 * @see org.mockftpserver.test.AbstractTest#setUp() 285 */ 286 protected void setUp() throws Exception { 287 super.setUp(); 288 remoteFile = new RemoteFile(); 289 remoteFile.setServer("localhost"); 290 stubFtpServer = new StubFtpServer(); 291 } 292 293 /** 294 * @see org.mockftpserver.test.AbstractTest#tearDown() 295 */ 296 protected void tearDown() throws Exception { 297 super.tearDown(); 298 stubFtpServer.stop(); 299 } 300 } 301 +------------------------------------------------------------------------------ 302 303 Things to note about the above test: 304 305 * The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started 306 there because it must be configured differently for each test. The <<<StubFtpServer>>> instance 307 is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails. 308 309 310 * FTP Command Reply Text ResourceBundle 311 312 The default text asociated with each FTP command reply code is contained within the 313 "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a 314 locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of 315 the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can 316 completely replace the ResourceBundle file by calling the calling the 317 <<<StubFtpServer.setReplyTextBaseName(String)>>> method. 318