blob: c342978057f8f6deef5504efcfd3cfd63d5770b5 [file] [log] [blame]
chrismair00dc7bd2014-05-11 21:21:28 +00001 --------------------------------------------------
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.