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