1 -------------------------------------------------- 2 FakeFtpServer Getting Started 3 -------------------------------------------------- 4 5 FakeFtpServer - Getting Started 6 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 8 <<FakeFtpServer>> is a "fake" implementation of an FTP server. It provides a high-level abstraction for 9 an FTP Server and is suitable for most testing and simulation scenarios. You define a virtual filesystem 10 (internal, in-memory) containing an arbitrary set of files and directories. These files and directories can 11 (optionally) have associated access permissions. You also configure a set of one or more user accounts that 12 control which users can login to the FTP server, and their home (default) directories. The user account is 13 also used when assigning file and directory ownership for new files. 14 15 <<FakeFtpServer>> processes FTP client requests and responds with reply codes and reply messages 16 consistent with its configured file system and user accounts, including file and directory permissions, 17 if they have been configured. 18 19 See the {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page for more information on 20 which features and scenarios are supported. 21 22 In general the steps for setting up and starting the <<<FakeFtpServer>>> are: 23 24 * Create a new <<<FakeFtpServer>>> instance, and optionally set the server control port (use a value of 0 25 to automatically choose a free port number). 26 27 * Create and configure a <<<FileSystem>>>, and attach to the <<<FakeFtpServer>>> instance. 28 29 * Create and configure one or more <<<UserAccount>>> objects and attach to the <<<FakeFtpServer>>> instance. 30 31 [] 32 33 Here is an example showing configuration and starting of an <<FakeFtpServer>> with a single user 34 account and a (simulated) Windows file system, defining a directory containing two files. 35 36 +------------------------------------------------------------------------------ 37 FakeFtpServer fakeFtpServer = new FakeFtpServer(); 38 fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data")); 39 40 FileSystem fileSystem = new WindowsFakeFileSystem(); 41 fileSystem.add(new DirectoryEntry("c:\\data")); 42 fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890")); 43 fileSystem.add(new FileEntry("c:\\data\\run.exe")); 44 fakeFtpServer.setFileSystem(fileSystem); 45 46 fakeFtpServer.start(); 47 +------------------------------------------------------------------------------ 48 49 If you are running on a system where the default port (21) is already in use or cannot be bound 50 from a user process (such as Unix), you probably need to use a different server control port. Use the 51 <<<FakeFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port 52 number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call 53 <<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port 54 number being used. Or, you can pass in a specific port number, such as 9187. 55 56 <<FakeFtpServer>> can be fully configured programmatically or within the 57 {{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container. 58 The {{{#Example}Example Test Using FakeFtpServer}} below illustrates programmatic configuration of 59 <<<FakeFtpServer>>>. Alternatively, the {{{#Spring}Configuration}} section later on illustrates how to use 60 the <Spring Framework> to configure a <<<FakeFtpServer>>> instance. 61 62 * {Example} Test Using FakeFtpServer 63 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 64 65 This section includes a simplified example of FTP client code to be tested, and a JUnit 66 test for it that programmatically configures and uses <<FakeFtpServer>>. 67 68 ** FTP Client Code 69 ~~~~~~~~~~~~~~~~~~ 70 71 The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote 72 ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the 73 {{{http://commons.apache.org/net/}Apache Commons Net}} framework. 74 75 +------------------------------------------------------------------------------ 76 public class RemoteFile { 77 78 public static final String USERNAME = "user"; 79 public static final String PASSWORD = "password"; 80 81 private String server; 82 private int port; 83 84 public String readFile(String filename) throws IOException { 85 86 FTPClient ftpClient = new FTPClient(); 87 ftpClient.connect(server, port); 88 ftpClient.login(USERNAME, PASSWORD); 89 90 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 91 boolean success = ftpClient.retrieveFile(filename, outputStream); 92 ftpClient.disconnect(); 93 94 if (!success) { 95 throw new IOException("Retrieve file failed: " + filename); 96 } 97 return outputStream.toString(); 98 } 99 100 public void setServer(String server) { 101 this.server = server; 102 } 103 104 public void setPort(int port) { 105 this.port = port; 106 } 107 108 // Other methods ... 109 } 110 +------------------------------------------------------------------------------ 111 112 ** JUnit Test For FTP Client Code Using FakeFtpServer 113 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 114 115 The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use 116 <<FakeFtpServer>>. 117 118 +------------------------------------------------------------------------------ 119 import org.mockftpserver.fake.filesystem.FileEntry; 120 import org.mockftpserver.fake.filesystem.FileSystem; 121 import org.mockftpserver.fake.filesystem.UnixFakeFileSystem; 122 import org.mockftpserver.fake.FakeFtpServer; 123 import org.mockftpserver.fake.UserAccount; 124 import org.mockftpserver.stub.example.RemoteFile; 125 import org.mockftpserver.test.AbstractTest; 126 import java.io.IOException; 127 import java.util.List; 128 129 public class RemoteFileTest extends AbstractTest { 130 131 private static final String HOME_DIR = "/"; 132 private static final String FILE = "/dir/sample.txt"; 133 private static final String CONTENTS = "abcdef 1234567890"; 134 135 private RemoteFile remoteFile; 136 private FakeFtpServer fakeFtpServer; 137 138 public void testReadFile() throws Exception { 139 String contents = remoteFile.readFile(FILE); 140 assertEquals("contents", CONTENTS, contents); 141 } 142 143 public void testReadFileThrowsException() { 144 try { 145 remoteFile.readFile("NoSuchFile.txt"); 146 fail("Expected IOException"); 147 } 148 catch (IOException expected) { 149 // Expected this 150 } 151 } 152 153 protected void setUp() throws Exception { 154 super.setUp(); 155 fakeFtpServer = new FakeFtpServer(); 156 fakeFtpServer.setServerControlPort(0); // use any free port 157 158 FileSystem fileSystem = new UnixFakeFileSystem(); 159 fileSystem.add(new FileEntry(FILE, CONTENTS)); 160 fakeFtpServer.setFileSystem(fileSystem); 161 162 UserAccount userAccount = new UserAccount(RemoteFile.USERNAME, RemoteFile.PASSWORD, HOME_DIR); 163 fakeFtpServer.addUserAccount(userAccount); 164 165 fakeFtpServer.start(); 166 int port = fakeFtpServer.getServerControlPort(); 167 168 remoteFile = new RemoteFile(); 169 remoteFile.setServer("localhost"); 170 remoteFile.setPort(port); 171 } 172 173 protected void tearDown() throws Exception { 174 super.tearDown(); 175 fakeFtpServer.stop(); 176 } 177 } 178 +------------------------------------------------------------------------------ 179 180 Things to note about the above test: 181 182 * The <<<FakeFtpServer>>> instance is created and started in the <<<setUp()>>> method and 183 stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails. 184 185 * The server control port is set to 0 using <<<fakeFtpServer.setServerControlPort(PORT)>>>. 186 This means it will dynamically choose a free port. This is necessary if you are running on a 187 system where the default port (21) is already in use or cannot be bound from a user process (such as Unix). 188 189 * The <<<UnixFakeFileSystem>>> filesystem is configured and attached to the <<<FakeFtpServer>>> instance 190 in the <<<setUp()>>> method. That includes creating a predefined <<<"/dir/sample.txt">>> file with the 191 specified file contents. The <<<UnixFakeFileSystem>>> has a <<<createParentDirectoriesAutomatically>>> 192 attribute, which defaults to <<<true>>>, meaning that parent directories will be created automatically, 193 as necessary. In this case, that means that the <<<"/">>> and <<<"/dir">>> parent directories will be created, 194 even though not explicitly specified. 195 196 * A single <<<UserAccount>>> with the specified username, password and home directory is configured and 197 attached to the <<<FakeFtpServer>>> instance in the <<<setUp()>>> method. That configured user ("user") 198 is the only one that will be able to sucessfully log in to the <<<FakeFtpServer>>>. 199 200 201 * {Spring} Configuration 202 ~~~~~~~~~~~~~~~~~~~~~~~~ 203 204 You can easily configure a <<<FakeFtpServer>>> instance in the 205 {{{http://www.springframework.org/}Spring Framework}} or another, similar dependency-injection container. 206 207 ** Simple Spring Configuration Example 208 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 209 210 The following example shows a <Spring> configuration file for a simple <<<FakeFtpServer>>> instance. 211 212 +------------------------------------------------------------------------------ 213 <?xml version="1.0" encoding="UTF-8"?> 214 215 <beans xmlns="http://www.springframework.org/schema/beans" 216 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 217 xsi:schemaLocation="http://www.springframework.org/schema/beans 218 http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> 219 220 <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer"> 221 <property name="serverControlPort" value="9981"/> 222 <property name="systemName" value="UNIX"/> 223 <property name="userAccounts"> 224 <list> 225 <bean class="org.mockftpserver.fake.UserAccount"> 226 <property name="username" value="joe"/> 227 <property name="password" value="password"/> 228 <property name="homeDirectory" value="/"/> 229 </bean> 230 </list> 231 </property> 232 233 <property name="fileSystem"> 234 <bean class="org.mockftpserver.fake.filesystem.UnixFakeFileSystem"> 235 <property name="createParentDirectoriesAutomatically" value="false"/> 236 <property name="entries"> 237 <list> 238 <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry"> 239 <property name="path" value="/"/> 240 </bean> 241 <bean class="org.mockftpserver.fake.filesystem.FileEntry"> 242 <property name="path" value="/File.txt"/> 243 <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/> 244 </bean> 245 </list> 246 </property> 247 </bean> 248 </property> 249 250 </bean> 251 252 </beans> 253 +------------------------------------------------------------------------------ 254 255 Things to note about the above example: 256 257 * The <<<FakeFtpServer>>> instance has a single user account for username "joe", password "password" 258 and home (default) directory of "/". 259 260 * A <<<UnixFakeFileSystem>>> instance is configured with a predefined directory of "/" and a 261 "/File.txt" file with the specified contents. 262 263 [] 264 265 And here is the Java code to load the above <Spring> configuration file and start the 266 configured <<FakeFtpServer>>. 267 268 +------------------------------------------------------------------------------ 269 ApplicationContext context = new ClassPathXmlApplicationContext("fakeftpserver-beans.xml"); 270 FakeFtpServer = (FakeFtpServer) context.getBean("FakeFtpServer"); 271 FakeFtpServer.start(); 272 +------------------------------------------------------------------------------ 273 274 275 ** Spring Configuration Example With File and Directory Permissions 276 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 277 278 The following example shows a <Spring> configuration file for a <<<FakeFtpServer>>> instance that 279 also configures file and directory permissions. This will enable the <<<FakeFtpServer>>> to reply 280 with proper error codes when the logged in user does not have the required permissions to access 281 directories or files. 282 283 +------------------------------------------------------------------------------ 284 <?xml version="1.0" encoding="UTF-8"?> 285 286 beans xmlns="http://www.springframework.org/schema/beans" 287 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 288 xsi:schemaLocation="http://www.springframework.org/schema/beans 289 http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> 290 291 <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer"> 292 <property name="serverControlPort" value="9981"/> 293 <property name="userAccounts"> 294 <list> 295 <bean class="org.mockftpserver.fake.UserAccount"> 296 <property name="username" value="joe"/> 297 <property name="password" value="password"/> 298 <property name="homeDirectory" value="c:\"/> 299 </bean> 300 </list> 301 </property> 302 303 <property name="fileSystem"> 304 <bean class="org.mockftpserver.fake.filesystem.WindowsFakeFileSystem"> 305 <property name="createParentDirectoriesAutomatically" value="false"/> 306 <property name="entries"> 307 <list> 308 <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry"> 309 <property name="path" value="c:\"/> 310 <property name="permissionsFromString" value="rwxrwxrwx"/> 311 <property name="owner" value="joe"/> 312 <property name="group" value="users"/> 313 </bean> 314 <bean class="org.mockftpserver.fake.filesystem.FileEntry"> 315 <property name="path" value="c:\File1.txt"/> 316 <property name="contents" value="1234567890"/> 317 <property name="permissionsFromString" value="rwxrwxrwx"/> 318 <property name="owner" value="peter"/> 319 <property name="group" value="users"/> 320 </bean> 321 <bean class="org.mockftpserver.fake.filesystem.FileEntry"> 322 <property name="path" value="c:\File2.txt"/> 323 <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/> 324 <property name="permissions"> 325 <bean class="org.mockftpserver.fake.filesystem.Permissions"> 326 <constructor-arg value="rwx------"/> 327 </bean> 328 </property> 329 <property name="owner" value="peter"/> 330 <property name="group" value="users"/> 331 </bean> 332 </list> 333 </property> 334 </bean> 335 </property> 336 337 </bean> 338 </beans> 339 +------------------------------------------------------------------------------ 340 341 342 Things to note about the above example: 343 344 * The <<<FakeFtpServer>>> instance is configured with a <<<WindowsFakeFileSystem>>> and a "c:\" root 345 directory containing two files. Permissions and owner/group are specified for that directory, as well 346 as the two predefined files contained within it. 347 348 * The permissions for "File1.txt" ("rwxrwxrwx") are specified using the "permissionsFromString" shortcut 349 method, while the permissions for "File2.txt" ("rwx------") are specified using the "permissions" setter, 350 which takes an instance of the <<<Permissions>>> class. Either method is fine. 351 352 [] 353 354 355 * Configuring Custom CommandHandlers 356 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 357 358 <<FakeFtpServer>> is intentionally designed to keep the lower-level details of FTP server implementation 359 hidden from the user. In most cases, you can simply define the files and directories in the file 360 system, configure one or more login users, and then fire up the server, expecting it to behave like 361 a <real> FTP server. 362 363 There are some cases, however, where you might want to further customize the internal behavior of the 364 server. Such cases might include: 365 366 * You want to have a particular FTP server command return a predetermined error reply 367 368 * You want to add support for a command that is not provided out of the box by <<FakeFtpServer>> 369 370 Note that if you need the FTP server to reply with entirely predetermined (canned) responses, then 371 you may want to consider using <<StubFtpServer>> instead. 372 373 374 ** Using a StaticReplyCommandHandler 375 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 376 377 You can use one of the <CommandHandler> classes defined within the <<<org.mockftpserver.core.command>>> 378 package to configure a custom <CommandHandler>. The following example uses the <<<StaticReplyCommandHandler>>> 379 from that package to add support for the FEAT command. Note that in this case, we are setting the 380 <CommandHandler> for a new command (i.e., one that is not supported out of the box by <<FakeFtpServer>>). 381 We could just as easily set the <CommandHandler> for an existing command, overriding the default <CommandHandler>. 382 383 +------------------------------------------------------------------------------ 384 import org.mockftpserver.core.command.StaticReplyCommandHandler 385 386 FakeFtpServer ftpServer = new FakeFtpServer() 387 // ... set up files, directories and user accounts as usual 388 389 StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features"); 390 ftpServer.setCommandHandler("FEAT", featCommandHandler); 391 392 // ... 393 ftpServer.start() 394 +------------------------------------------------------------------------------ 395 396 397 ** Using a Stub CommandHandler 398 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 399 400 You can also use a <<StubFtpServer>> <CommandHandler> -- i.e., one defined within the 401 <<<org.mockftpserver.stub.command>>> package. The following example uses the <stub> version of the 402 <<<CwdCommandHandler>>> from that package. 403 404 +------------------------------------------------------------------------------ 405 import org.mockftpserver.stub.command.CwdCommandHandler 406 407 FakeFtpServer ftpServer = new FakeFtpServer() 408 // ... set up files, directories and user accounts as usual 409 410 final int REPLY_CODE = 502; 411 CwdCommandHandler cwdCommandHandler = new CwdCommandHandler(); 412 cwdCommandHandler.setReplyCode(REPLY_CODE); 413 ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler); 414 415 // ... 416 ftpServer.start() 417 +------------------------------------------------------------------------------ 418 419 420 ** Creating Your Own Custom CommandHandler Class 421 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 422 423 If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create 424 your own custom <CommandHandler> class. The only requirement is that it implement the 425 <<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from 426 inheriting from one of the existing abstract <CommandHandler> superclasses, such as 427 <<<org.mockftpserver.core.command.AbstractStaticReplyCommandHandler>>> or 428 <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for 429 more information. 430 431 432 * FTP Command Reply Text ResourceBundle 433 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 434 435 The default text asociated with each FTP command reply code is contained within the 436 "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a 437 locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of 438 the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can 439 completely replace the ResourceBundle file by calling the calling the 440 <<<FakeFtpServer.setReplyTextBaseName(String)>>> method. 441 442 * SLF4J Configuration Required to See Log Output 443 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 444 445 Note that <<FakeFtpServer>> uses {{{http://www.slf4j.org/}SLF4J}} for logging. If you want to 446 see the logging output, then you must configure <<SLF4J>>. (If no binding is found on the class 447 path, then <<SLF4J>> will default to a no-operation implementation.) 448 449 See the {{{http://www.slf4j.org/manual.html}SLF4J User Manual}} for more information. 450