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