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