summaryrefslogtreecommitdiff
path: root/tags/2.3/src/site/apt/stubftpserver-getting-started.apt
blob: 4ba17c056631990b2d828df37538ebcbff9c481f (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
		--------------------------------------------------
					StubFtpServer Getting Started
		--------------------------------------------------

StubFtpServer - Getting Started
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by 
  implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR, 
  DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes, 
  allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can 
  also be interrogated to verify command invocation data such as command parameters and timestamps.

  <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured 
  programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container.

  Here is how to start the <<StubFtpServer>> with the default configuration. This will return 
  success reply codes, and return empty data (for retrieved files, directory listings, etc.).

+------------------------------------------------------------------------------  
StubFtpServer stubFtpServer = new StubFtpServer();
stubFtpServer.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 will need to use a different server control port. Use the
  <<<StubFtpServer.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.

* CommandHandlers
~~~~~~~~~~~~~~~~~

  <CommandHandler>s are the heart of the <<StubFtpServer>>.

  <<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server 
  command. See the list of <CommandHandler> classes associated with FTP server commands in 
  {{{stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}.

  You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the
  <<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command
  name. For example:
  
+------------------------------------------------------------------------------  
PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
+------------------------------------------------------------------------------  

  You can replace the existing <CommandHandler> defined for an FTP server command by calling the
  <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing 
  in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the 
  <<<CommandHandler>>> instance. For example:
  
+------------------------------------------------------------------------------  
PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
pwdCommandHandler.setDirectory("some/dir");
stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
+------------------------------------------------------------------------------  


** Generic CommandHandlers
~~~~~~~~~~~~~~~~~~~~~~~~~~

  <<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace
  the default command handler for an FTP command. See the Javadoc for more information.
  
  * <<StaticReplyCommadHandler>>

    <<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply 
    code and text. This can be a useful replacement for a default <CommandHandler> if you want a 
    certain FTP command to always send back an error reply code.
  
  * <<SimpleCompositeCommandHandler>>

    <<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal 
    ordered list of <CommandHandler>s to which it delegates. Starting with the first 
    <CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to) 
    the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list.


** Configuring CommandHandler for a New (Unsupported) Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  If you want to add support for a command that is not provided out of the box by <<StubFtpServer>>,
  you can create a <CommandHandler> instance and set it within the <<StubFtpServer>> using the
  <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method in the
  same way that you replace an existing <CommandHandler> (see above). The following example uses
  the <<<StaticReplyCommandHandler>>> to add support for the FEAT command.

+------------------------------------------------------------------------------
final String FEAT_TEXT = "Extensions supported:\n" +
        "MLST size*;create;modify*;perm;media-type\n" +
        "SIZE\n" +
        "COMPRESSION\n" +
        "END";
StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT);
stubFtpServer.setCommandHandler("FEAT", featCommandHandler);
+------------------------------------------------------------------------------


** 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.stub.command.AbstractStubCommandHandler>>> or
  <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for
  more information.


* Retrieving Command Invocation Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Each predefined <<StubFtpServer>> <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one
  for each time the <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>> 
  that triggered the invocation (containing the command name and parameters), as well as the invocation
  timestamp and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional
  <CommandHandler>-specific data. See the Javadoc for more information.
  
  You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the
  <<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired
  invocation. You can get the number of invocations for a <CommandHandler> by calling
  <<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates 
  retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>.
  

* {Example} Test Using StubFtpServer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

** 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 {

    private String server;

    public String readFile(String filename) throws SocketException, IOException {

        FTPClient ftpClient = new FTPClient();
        ftpClient.connect(server);

        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;
    }
    
    // Other methods ...
}
+------------------------------------------------------------------------------  

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

  The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use 
  <<StubFtpServer>>. The test illustrates replacing the default <CommandHandler> with
  a customized handler.

+------------------------------------------------------------------------------  
import java.io.IOException;
import org.mockftpserver.core.command.InvocationRecord;
import org.mockftpserver.stub.StubFtpServer;
import org.mockftpserver.stub.command.RetrCommandHandler;
import org.mockftpserver.test.AbstractTest;

public class RemoteFileTest extends AbstractTest {

    private static final String FILENAME = "dir/sample.txt";

    private RemoteFile remoteFile;
    private StubFtpServer stubFtpServer;
    
    public void testReadFile() throws Exception {

        final String CONTENTS = "abcdef 1234567890";

        // Replace the default RETR CommandHandler; customize returned file contents
        RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
        retrCommandHandler.setFileContents(CONTENTS);
        stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
        
        stubFtpServer.start();
        
        String contents = remoteFile.readFile(FILENAME);

        // Verify returned file contents
        assertEquals("contents", CONTENTS, contents);
        
        // Verify the submitted filename
        InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0);
        String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY);
        assertEquals("filename", FILENAME, filename);
    }

    /**
     * Test the readFile() method when the FTP transfer fails (returns a non-success reply code) 
     */
    public void testReadFileThrowsException() {

        // Replace the default RETR CommandHandler; return failure reply code
        RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
        retrCommandHandler.setFinalReplyCode(550);
        stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
        
        stubFtpServer.start();

        try {
            remoteFile.readFile(FILENAME);
            fail("Expected IOException");
        }
        catch (IOException expected) {
            // Expected this
        }
    }
    
    protected void setUp() throws Exception {
        super.setUp();
        remoteFile = new RemoteFile();
        remoteFile.setServer("localhost");
        stubFtpServer = new StubFtpServer();
    }

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

  Things to note about the above test:
  
  * The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started
    there because it must be configured differently for each test. The <<<StubFtpServer>>> instance 
    is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.
  

* Spring Configuration
~~~~~~~~~~~~~~~~~~~~~~

  You can easily configure a <<StubFtpServer>> instance in the
  {{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring>
  configuration file.

+------------------------------------------------------------------------------
<?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="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer">

    <property name="commandHandlers">
      <map>
        <entry key="LIST">
          <bean class="org.mockftpserver.stub.command.ListCommandHandler">
            <property name="directoryListing">
              <value>
                11-09-01 12:30PM  406348 File2350.log
                11-01-01 1:30PM &lt;DIR&gt; 0 archive
              </value>
            </property>
          </bean>
        </entry>

        <entry key="PWD">
          <bean class="org.mockftpserver.stub.command.PwdCommandHandler">
            <property name="directory" value="foo/bar" />
          </bean>
        </entry>

        <entry key="DELE">
          <bean class="org.mockftpserver.stub.command.DeleCommandHandler">
            <property name="replyCode" value="450" />
          </bean>
        </entry>

        <entry key="RETR">
          <bean class="org.mockftpserver.stub.command.RetrCommandHandler">
            <property name="fileContents"
              value="Sample file contents line 1&#10;Line 2&#10;Line 3"/>
          </bean>
        </entry>

      </map>
    </property>
  </bean>

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

  This example overrides the default handlers for the following FTP commands:

  * LIST - replies with a predefined directory listing

  * PWD - replies with a predefined directory pathname

  * DELE - replies with an error reply code (450)

  * RETR - replies with predefined contents for a retrieved file

  []

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

+------------------------------------------------------------------------------
ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml");
stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer");
stubFtpServer.start();
+------------------------------------------------------------------------------


* 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 
  <<<StubFtpServer.setReplyTextBaseName(String)>>> method.