• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 (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+------------------------------------------------------------------------------
37FakeFtpServer fakeFtpServer = new FakeFtpServer();
38fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data"));
39
40FileSystem fileSystem = new WindowsFakeFileSystem();
41fileSystem.add(new DirectoryEntry("c:\\data"));
42fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
43fileSystem.add(new FileEntry("c:\\data\\run.exe"));
44fakeFtpServer.setFileSystem(fileSystem);
45
46fakeFtpServer.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+------------------------------------------------------------------------------
76public 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+------------------------------------------------------------------------------
119import org.mockftpserver.fake.filesystem.FileEntry;
120import org.mockftpserver.fake.filesystem.FileSystem;
121import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
122import org.mockftpserver.fake.FakeFtpServer;
123import org.mockftpserver.fake.UserAccount;
124import org.mockftpserver.stub.example.RemoteFile;
125import org.mockftpserver.test.AbstractTest;
126import java.io.IOException;
127import java.util.List;
128
129public 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+------------------------------------------------------------------------------
269ApplicationContext context = new ClassPathXmlApplicationContext("fakeftpserver-beans.xml");
270FakeFtpServer = (FakeFtpServer) context.getBean("FakeFtpServer");
271FakeFtpServer.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
286beans 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+------------------------------------------------------------------------------
384import org.mockftpserver.core.command.StaticReplyCommandHandler
385
386FakeFtpServer ftpServer = new FakeFtpServer()
387// ... set up files, directories and user accounts as usual
388
389StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
390ftpServer.setCommandHandler("FEAT", featCommandHandler);
391
392// ...
393ftpServer.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+------------------------------------------------------------------------------
405import org.mockftpserver.stub.command.CwdCommandHandler
406
407FakeFtpServer ftpServer = new FakeFtpServer()
408// ... set up files, directories and user accounts as usual
409
410final int REPLY_CODE = 502;
411CwdCommandHandler cwdCommandHandler = new CwdCommandHandler();
412cwdCommandHandler.setReplyCode(REPLY_CODE);
413ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
414
415// ...
416ftpServer.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