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