summaryrefslogtreecommitdiff
path: root/tags/2.3/src/site/apt/fakeftpserver-getting-started.apt
blob: 9dae7f0d906e55bb604a40867640337ec59167b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
		--------------------------------------------------
					FakeFtpServer Getting Started
		--------------------------------------------------

FakeFtpServer - Getting Started
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  <<FakeFtpServer>> is a "fake" implementation of an FTP server. It provides a high-level abstraction for
  an FTP Server and is suitable for most testing and simulation scenarios. You define a virtual filesystem
  (internal, in-memory) containing an arbitrary set of files and directories. These files and directories can
  (optionally) have associated access permissions. You also configure a set of one or more user accounts that
  control which users can login to the FTP server, and their home (default) directories. The user account is
  also used when assigning file and directory ownership for new files.

  <<FakeFtpServer>> processes FTP client requests and responds with reply codes and reply messages
  consistent with its configured file system and user accounts, including file and directory permissions,
  if they have been configured.

  See the {{{fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page for more information on
  which features and scenarios are supported.

  In general the steps for setting up and starting the <<<FakeFtpServer>>> are:

  * Create a new <<<FakeFtpServer>>> instance, and optionally set the server control port (use a value of 0
    to automatically choose a free port number).

  * Create and configure a <<<FileSystem>>>, and attach to the <<<FakeFtpServer>>> instance.

  * Create and configure one or more <<<UserAccount>>> objects and attach to the <<<FakeFtpServer>>> instance.

  []

  Here is an example showing configuration and starting of an <<FakeFtpServer>> with a single user
  account and a (simulated) Windows file system, defining a directory containing two files.

+------------------------------------------------------------------------------
FakeFtpServer fakeFtpServer = new FakeFtpServer();
fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data"));

FileSystem fileSystem = new WindowsFakeFileSystem();
fileSystem.add(new DirectoryEntry("c:\\data"));
fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
fileSystem.add(new FileEntry("c:\\data\\run.exe"));
fakeFtpServer.setFileSystem(fileSystem);

fakeFtpServer.start();
+------------------------------------------------------------------------------

  If you are running on a system where the default port (21) is already in use or cannot be bound
  from a user process (such as Unix), you probably need to use a different server control port. Use the
  <<<FakeFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port
  number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call
  <<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port
  number being used. Or, you can pass in a specific port number, such as 9187.

  <<FakeFtpServer>>  can be fully configured programmatically or within the
  {{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container.
  The {{{#Example}Example Test Using FakeFtpServer}} below illustrates programmatic configuration of
  <<<FakeFtpServer>>>. Alternatively, the {{{#Spring}Configuration}} section later on illustrates how to use
  the <Spring Framework> to configure a <<<FakeFtpServer>>> instance.

* {Example} Test Using FakeFtpServer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  This section includes a simplified example of FTP client code to be tested, and a JUnit 
  test for it that programmatically configures and uses <<FakeFtpServer>>.

** FTP Client Code
~~~~~~~~~~~~~~~~~~

  The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote 
  ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the
  {{{http://commons.apache.org/net/}Apache Commons Net}} framework.

+------------------------------------------------------------------------------  
public class RemoteFile {

    public static final String USERNAME = "user";
    public static final String PASSWORD = "password";

    private String server;
    private int port;

    public String readFile(String filename) throws IOException {

        FTPClient ftpClient = new FTPClient();
        ftpClient.connect(server, port);
        ftpClient.login(USERNAME, PASSWORD);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        boolean success = ftpClient.retrieveFile(filename, outputStream);
        ftpClient.disconnect();

        if (!success) {
            throw new IOException("Retrieve file failed: " + filename);
        }
        return outputStream.toString();
    }

    public void setServer(String server) {
        this.server = server;
    }

    public void setPort(int port) {
        this.port = port;
    }

    // Other methods ...
}
+------------------------------------------------------------------------------

** JUnit Test For FTP Client Code Using FakeFtpServer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use 
  <<FakeFtpServer>>.

+------------------------------------------------------------------------------  
import org.mockftpserver.fake.filesystem.FileEntry;
import org.mockftpserver.fake.filesystem.FileSystem;
import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
import org.mockftpserver.fake.FakeFtpServer;
import org.mockftpserver.fake.UserAccount;
import org.mockftpserver.stub.example.RemoteFile;
import org.mockftpserver.test.AbstractTest;
import java.io.IOException;
import java.util.List;

public class RemoteFileTest extends AbstractTest {

    private static final String HOME_DIR = "/";
    private static final String FILE = "/dir/sample.txt";
    private static final String CONTENTS = "abcdef 1234567890";

    private RemoteFile remoteFile;
    private FakeFtpServer fakeFtpServer;

    public void testReadFile() throws Exception {
        String contents = remoteFile.readFile(FILE);
        assertEquals("contents", CONTENTS, contents);
    }

    public void testReadFileThrowsException() {
        try {
            remoteFile.readFile("NoSuchFile.txt");
            fail("Expected IOException");
        }
        catch (IOException expected) {
            // Expected this
        }
    }

    protected void setUp() throws Exception {
        super.setUp();
        fakeFtpServer = new FakeFtpServer();
        fakeFtpServer.setServerControlPort(0);  // use any free port

        FileSystem fileSystem = new UnixFakeFileSystem();
        fileSystem.add(new FileEntry(FILE, CONTENTS));
        fakeFtpServer.setFileSystem(fileSystem);

        UserAccount userAccount = new UserAccount(RemoteFile.USERNAME, RemoteFile.PASSWORD, HOME_DIR);
        fakeFtpServer.addUserAccount(userAccount);

        fakeFtpServer.start();
        int port = fakeFtpServer.getServerControlPort();

        remoteFile = new RemoteFile();
        remoteFile.setServer("localhost");
        remoteFile.setPort(port);
    }

    protected void tearDown() throws Exception {
        super.tearDown();
        fakeFtpServer.stop();
    }
}
+------------------------------------------------------------------------------

  Things to note about the above test:
  
  * The <<<FakeFtpServer>>> instance is created and started in the <<<setUp()>>> method and
    stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.

  * The server control port is set to 0 using <<<fakeFtpServer.setServerControlPort(PORT)>>>.
    This means it will dynamically choose a free port. This is necessary if you are running on a
    system where the default port (21) is already in use or cannot be bound from a user process (such as Unix).

  * The <<<UnixFakeFileSystem>>> filesystem is configured and attached to the <<<FakeFtpServer>>> instance
    in the <<<setUp()>>> method. That includes creating a predefined <<<"/dir/sample.txt">>> file with the
    specified file contents. The <<<UnixFakeFileSystem>>> has a <<<createParentDirectoriesAutomatically>>>
    attribute, which defaults to <<<true>>>, meaning that parent directories will be created automatically,
    as necessary. In this case, that means that the <<<"/">>> and <<<"/dir">>> parent directories will be created,
    even though not explicitly specified.

  * A single <<<UserAccount>>> with the specified username, password and home directory is configured and
    attached to the <<<FakeFtpServer>>> instance in the <<<setUp()>>> method. That configured user ("user")
    is the only one that will be able to sucessfully log in to the <<<FakeFtpServer>>>. 


* {Spring} Configuration
~~~~~~~~~~~~~~~~~~~~~~~~

  You can easily configure a <<<FakeFtpServer>>> instance in the
  {{{http://www.springframework.org/}Spring Framework}} or another, similar dependency-injection container.

** Simple Spring Configuration Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The following example shows a <Spring> configuration file for a simple <<<FakeFtpServer>>> instance.

+------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       		http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
        <property name="serverControlPort" value="9981"/>
        <property name="systemName" value="UNIX"/>
        <property name="userAccounts">
            <list>
                <bean class="org.mockftpserver.fake.UserAccount">
                    <property name="username" value="joe"/>
                    <property name="password" value="password"/>
                    <property name="homeDirectory" value="/"/>
                </bean>
            </list>
        </property>

        <property name="fileSystem">
            <bean class="org.mockftpserver.fake.filesystem.UnixFakeFileSystem">
                <property name="createParentDirectoriesAutomatically" value="false"/>
                <property name="entries">
                    <list>
                        <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
                            <property name="path" value="/"/>
                        </bean>
                        <bean class="org.mockftpserver.fake.filesystem.FileEntry">
                            <property name="path" value="/File.txt"/>
                            <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </property>

    </bean>

</beans>
+------------------------------------------------------------------------------

  Things to note about the above example:

  * The <<<FakeFtpServer>>> instance has a single user account for username "joe", password "password"
    and home (default) directory of "/".

  * A <<<UnixFakeFileSystem>>> instance is configured with a predefined directory of "/" and a
    "/File.txt" file with the specified contents.

  []

  And here is the Java code to load the above <Spring> configuration file and start the
  configured <<FakeFtpServer>>.

+------------------------------------------------------------------------------
ApplicationContext context = new ClassPathXmlApplicationContext("fakeftpserver-beans.xml");
FakeFtpServer = (FakeFtpServer) context.getBean("FakeFtpServer");
FakeFtpServer.start();
+------------------------------------------------------------------------------


** Spring Configuration Example With File and Directory Permissions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The following example shows a <Spring> configuration file for a <<<FakeFtpServer>>> instance that
  also configures file and directory permissions. This will enable the <<<FakeFtpServer>>> to reply
  with proper error codes when the logged in user does not have the required permissions to access
  directories or files.

+------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>

beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       		http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
        <property name="serverControlPort" value="9981"/>
        <property name="userAccounts">
            <list>
                <bean class="org.mockftpserver.fake.UserAccount">
                    <property name="username" value="joe"/>
                    <property name="password" value="password"/>
                    <property name="homeDirectory" value="c:\"/>
                </bean>
            </list>
        </property>

        <property name="fileSystem">
            <bean class="org.mockftpserver.fake.filesystem.WindowsFakeFileSystem">
                <property name="createParentDirectoriesAutomatically" value="false"/>
                <property name="entries">
                    <list>
                        <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
                            <property name="path" value="c:\"/>
                            <property name="permissionsFromString" value="rwxrwxrwx"/>
                            <property name="owner" value="joe"/>
                            <property name="group" value="users"/>
                        </bean>
                        <bean class="org.mockftpserver.fake.filesystem.FileEntry">
                            <property name="path" value="c:\File1.txt"/>
                            <property name="contents" value="1234567890"/>
                            <property name="permissionsFromString" value="rwxrwxrwx"/>
                            <property name="owner" value="peter"/>
                            <property name="group" value="users"/>
                        </bean>
                        <bean class="org.mockftpserver.fake.filesystem.FileEntry">
                            <property name="path" value="c:\File2.txt"/>
                            <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
                            <property name="permissions">
                                <bean class="org.mockftpserver.fake.filesystem.Permissions">
                                    <constructor-arg value="rwx------"/>
                                </bean>
                            </property>
                            <property name="owner" value="peter"/>
                            <property name="group" value="users"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </property>

    </bean>
</beans>
+------------------------------------------------------------------------------


  Things to note about the above example:

  * The <<<FakeFtpServer>>> instance is configured with a <<<WindowsFakeFileSystem>>> and a "c:\" root
    directory containing two files. Permissions and owner/group are specified for that directory, as well
    as the two predefined files contained within it.

  * The permissions for "File1.txt" ("rwxrwxrwx") are specified using the "permissionsFromString" shortcut
    method, while the permissions for "File2.txt" ("rwx------") are specified using the "permissions" setter,
    which takes an instance of the <<<Permissions>>> class. Either method is fine.

  []


* Configuring Custom CommandHandlers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  <<FakeFtpServer>> is intentionally designed to keep the lower-level details of FTP server implementation
  hidden from the user. In most cases, you can simply define the files and directories in the file
  system, configure one or more login users, and then fire up the server, expecting it to behave like
  a <real> FTP server.

  There are some cases, however, where you might want to further customize the internal behavior of the
  server. Such cases might include:

  * You want to have a particular FTP server command return a predetermined error reply

  * You want to add support for a command that is not provided out of the box by <<FakeFtpServer>>

  Note that if you need the FTP server to reply with entirely predetermined (canned) responses, then
  you may want to consider using <<StubFtpServer>> instead.  


** Using a StaticReplyCommandHandler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can use one of the <CommandHandler> classes defined within the <<<org.mockftpserver.core.command>>>
  package to configure a custom <CommandHandler>. The following example uses the <<<StaticReplyCommandHandler>>>
  from that package to add support for the FEAT command. Note that in this case, we are setting the
  <CommandHandler> for a new command (i.e., one that is not supported out of the box by <<FakeFtpServer>>).
  We could just as easily set the <CommandHandler> for an existing command, overriding the default <CommandHandler>.

+------------------------------------------------------------------------------
import org.mockftpserver.core.command.StaticReplyCommandHandler

FakeFtpServer ftpServer = new FakeFtpServer()
// ... set up files, directories and user accounts as usual

StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
ftpServer.setCommandHandler("FEAT", featCommandHandler);

// ...
ftpServer.start()
+------------------------------------------------------------------------------


** Using a Stub CommandHandler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can also use a <<StubFtpServer>> <CommandHandler> -- i.e., one defined within the
  <<<org.mockftpserver.stub.command>>> package. The following example uses the <stub> version of the
  <<<CwdCommandHandler>>> from that package.

+------------------------------------------------------------------------------
import org.mockftpserver.stub.command.CwdCommandHandler

FakeFtpServer ftpServer = new FakeFtpServer()
// ... set up files, directories and user accounts as usual

final int REPLY_CODE = 502;
CwdCommandHandler cwdCommandHandler = new CwdCommandHandler();
cwdCommandHandler.setReplyCode(REPLY_CODE);
ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);

// ...
ftpServer.start()
+------------------------------------------------------------------------------


** Creating Your Own Custom CommandHandler Class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create
  your own custom <CommandHandler> class. The only requirement is that it implement the
  <<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from
  inheriting from one of the existing abstract <CommandHandler> superclasses, such as
  <<<org.mockftpserver.core.command.AbstractStaticReplyCommandHandler>>> or
  <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for
  more information.


* FTP Command Reply Text ResourceBundle
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The default text asociated with each FTP command reply code is contained within the
  "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
  locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of 
  the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can 
  completely replace the ResourceBundle file by calling the calling the 
  <<<FakeFtpServer.setReplyTextBaseName(String)>>> method.

* Log4J Configuration Required to See Log Output
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Note that <<FakeFtpServer>> uses {{{http://logging.apache.org/log4j/index.html}Log4J}} for
  logging. If you want to see the logging output, then you must configure <<Log4J>>. There are several
  options, described on the {{{http://logging.apache.org/log4j/index.html}Log4J}} site. The easiest
  way is to put a "log4j.properties" file on your classpath. The example "log4j.properties" shown
  below writes out only INFO, WARN and ERROR messages, and only to the CONSOLE (System.out).

+------------------------------------------------------------------------------
log4j.rootCategory=INFO, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %c{1} [%t] %p - %m%n
+------------------------------------------------------------------------------