Home | History | Annotate | Download | only in apt
      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