blob: cbe23396286b69ee8030790bac00a8f070860edd [file] [log] [blame]
chrismair00dc7bd2014-05-11 21:21:28 +00001 --------------------------------------------------
2 StubFtpServer Getting Started
3 --------------------------------------------------
4
5StubFtpServer - Getting Started
6~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7
8 <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by
9 implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR,
10 DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes,
11 allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can
12 also be interrogated to verify command invocation data such as command parameters and timestamps.
13
14 <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured
15 programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container.
16
17 Here is how to start the <<StubFtpServer>> with the default configuration. This will return
18 success reply codes, and return empty data (for retrieved files, directory listings, etc.).
19
20+------------------------------------------------------------------------------
21StubFtpServer stubFtpServer = new StubFtpServer();
22stubFtpServer.start();
23+------------------------------------------------------------------------------
24
25 If you are running on a system where the default port (21) is already in use or cannot be bound
26 from a user process (such as Unix), you will need to use a different server control port. Use the
27 <<<StubFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port
28 number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call
29 <<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port
30 number being used. Or, you can pass in a specific port number, such as 9187.
31
32* CommandHandlers
33~~~~~~~~~~~~~~~~~
34
35 <CommandHandler>s are the heart of the <<StubFtpServer>>.
36
37 <<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server
38 command. See the list of <CommandHandler> classes associated with FTP server commands in
39 {{{./stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}.
40
41 You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the
42 <<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command
43 name. For example:
44
45+------------------------------------------------------------------------------
46PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
47+------------------------------------------------------------------------------
48
49 You can replace the existing <CommandHandler> defined for an FTP server command by calling the
50 <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing
51 in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the
52 <<<CommandHandler>>> instance. For example:
53
54+------------------------------------------------------------------------------
55PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
56pwdCommandHandler.setDirectory("some/dir");
57stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
58+------------------------------------------------------------------------------
59
60
61** Generic CommandHandlers
62~~~~~~~~~~~~~~~~~~~~~~~~~~
63
64 <<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace
65 the default command handler for an FTP command. See the Javadoc for more information.
66
67 * <<StaticReplyCommadHandler>>
68
69 <<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply
70 code and text. This can be a useful replacement for a default <CommandHandler> if you want a
71 certain FTP command to always send back an error reply code.
72
73 * <<SimpleCompositeCommandHandler>>
74
75 <<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal
76 ordered list of <CommandHandler>s to which it delegates. Starting with the first
77 <CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to)
78 the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list.
79
80
81** Configuring CommandHandler for a New (Unsupported) Command
82~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
83
84 If you want to add support for a command that is not provided out of the box by <<StubFtpServer>>,
85 you can create a <CommandHandler> instance and set it within the <<StubFtpServer>> using the
86 <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method in the
87 same way that you replace an existing <CommandHandler> (see above). The following example uses
88 the <<<StaticReplyCommandHandler>>> to add support for the FEAT command.
89
90+------------------------------------------------------------------------------
91final String FEAT_TEXT = "Extensions supported:\n" +
92 "MLST size*;create;modify*;perm;media-type\n" +
93 "SIZE\n" +
94 "COMPRESSION\n" +
95 "END";
96StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT);
97stubFtpServer.setCommandHandler("FEAT", featCommandHandler);
98+------------------------------------------------------------------------------
99
100
101** Creating Your Own Custom CommandHandler Class
102~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
103
104 If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create
105 your own custom <CommandHandler> class. The only requirement is that it implement the
106 <<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from
107 inheriting from one of the existing abstract <CommandHandler> superclasses, such as
108 <<<org.mockftpserver.stub.command.AbstractStubCommandHandler>>> or
109 <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for
110 more information.
111
112
113* Retrieving Command Invocation Data
114~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
115
116 Each predefined <<StubFtpServer>> <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one
117 for each time the <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>>
118 that triggered the invocation (containing the command name and parameters), as well as the invocation
119 timestamp and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional
120 <CommandHandler>-specific data. See the Javadoc for more information.
121
122 You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the
123 <<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired
124 invocation. You can get the number of invocations for a <CommandHandler> by calling
125 <<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates
126 retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>.
127
128
129* {Example} Test Using StubFtpServer
130~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131
132 This section includes a simplified example of FTP client code to be tested, and a JUnit
133 test for it that uses <<StubFtpServer>>.
134
135** FTP Client Code
136~~~~~~~~~~~~~~~~~~
137
138 The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote
139 ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the
140 {{{http://commons.apache.org/net/}Apache Commons Net}} framework.
141
142+------------------------------------------------------------------------------
143public class RemoteFile {
144
145 private String server;
146
147 public String readFile(String filename) throws SocketException, IOException {
148
149 FTPClient ftpClient = new FTPClient();
150 ftpClient.connect(server);
151
152 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
153 boolean success = ftpClient.retrieveFile(filename, outputStream);
154 ftpClient.disconnect();
155
156 if (!success) {
157 throw new IOException("Retrieve file failed: " + filename);
158 }
159 return outputStream.toString();
160 }
161
162 public void setServer(String server) {
163 this.server = server;
164 }
165
166 // Other methods ...
167}
168+------------------------------------------------------------------------------
169
170** JUnit Test For FTP Client Code Using StubFtpServer
171~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
172
173 The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use
174 <<StubFtpServer>>. The test illustrates replacing the default <CommandHandler> with
175 a customized handler.
176
177+------------------------------------------------------------------------------
178import java.io.IOException;
179import org.mockftpserver.core.command.InvocationRecord;
180import org.mockftpserver.stub.StubFtpServer;
181import org.mockftpserver.stub.command.RetrCommandHandler;
182import org.mockftpserver.test.AbstractTest;
183
184public class RemoteFileTest extends AbstractTest {
185
186 private static final String FILENAME = "dir/sample.txt";
187
188 private RemoteFile remoteFile;
189 private StubFtpServer stubFtpServer;
190
191 public void testReadFile() throws Exception {
192
193 final String CONTENTS = "abcdef 1234567890";
194
195 // Replace the default RETR CommandHandler; customize returned file contents
196 RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
197 retrCommandHandler.setFileContents(CONTENTS);
198 stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
199
200 stubFtpServer.start();
201
202 String contents = remoteFile.readFile(FILENAME);
203
204 // Verify returned file contents
205 assertEquals("contents", CONTENTS, contents);
206
207 // Verify the submitted filename
208 InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0);
209 String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY);
210 assertEquals("filename", FILENAME, filename);
211 }
212
213 /**
214 * Test the readFile() method when the FTP transfer fails (returns a non-success reply code)
215 */
216 public void testReadFileThrowsException() {
217
218 // Replace the default RETR CommandHandler; return failure reply code
219 RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
220 retrCommandHandler.setFinalReplyCode(550);
221 stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
222
223 stubFtpServer.start();
224
225 try {
226 remoteFile.readFile(FILENAME);
227 fail("Expected IOException");
228 }
229 catch (IOException expected) {
230 // Expected this
231 }
232 }
233
234 protected void setUp() throws Exception {
235 super.setUp();
236 remoteFile = new RemoteFile();
237 remoteFile.setServer("localhost");
238 stubFtpServer = new StubFtpServer();
239 }
240
241 protected void tearDown() throws Exception {
242 super.tearDown();
243 stubFtpServer.stop();
244 }
245}
246+------------------------------------------------------------------------------
247
248 Things to note about the above test:
249
250 * The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started
251 there because it must be configured differently for each test. The <<<StubFtpServer>>> instance
252 is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.
253
254
255* Spring Configuration
256~~~~~~~~~~~~~~~~~~~~~~
257
258 You can easily configure a <<StubFtpServer>> instance in the
259 {{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring>
260 configuration file.
261
262+------------------------------------------------------------------------------
263<?xml version="1.0" encoding="UTF-8"?>
264
265<beans xmlns="http://www.springframework.org/schema/beans"
266 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
267 xsi:schemaLocation="http://www.springframework.org/schema/beans
268 http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
269
270 <bean id="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer">
271
272 <property name="commandHandlers">
273 <map>
274 <entry key="LIST">
275 <bean class="org.mockftpserver.stub.command.ListCommandHandler">
276 <property name="directoryListing">
277 <value>
278 11-09-01 12:30PM 406348 File2350.log
279 11-01-01 1:30PM &lt;DIR&gt; 0 archive
280 </value>
281 </property>
282 </bean>
283 </entry>
284
285 <entry key="PWD">
286 <bean class="org.mockftpserver.stub.command.PwdCommandHandler">
287 <property name="directory" value="foo/bar" />
288 </bean>
289 </entry>
290
291 <entry key="DELE">
292 <bean class="org.mockftpserver.stub.command.DeleCommandHandler">
293 <property name="replyCode" value="450" />
294 </bean>
295 </entry>
296
297 <entry key="RETR">
298 <bean class="org.mockftpserver.stub.command.RetrCommandHandler">
299 <property name="fileContents"
300 value="Sample file contents line 1&#10;Line 2&#10;Line 3"/>
301 </bean>
302 </entry>
303
304 </map>
305 </property>
306 </bean>
307
308</beans>
309+------------------------------------------------------------------------------
310
311 This example overrides the default handlers for the following FTP commands:
312
313 * LIST - replies with a predefined directory listing
314
315 * PWD - replies with a predefined directory pathname
316
317 * DELE - replies with an error reply code (450)
318
319 * RETR - replies with predefined contents for a retrieved file
320
321 []
322
323 And here is the Java code to load the above <Spring> configuration file and start the
324 configured <<StubFtpServer>>.
325
326+------------------------------------------------------------------------------
327ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml");
328stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer");
329stubFtpServer.start();
330+------------------------------------------------------------------------------
331
332
333* FTP Command Reply Text ResourceBundle
334~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
335
336 The default text asociated with each FTP command reply code is contained within the
337 "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
338 locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
339 the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
340 completely replace the ResourceBundle file by calling the calling the
341 <<<StubFtpServer.setReplyTextBaseName(String)>>> method.