• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.test;
6 
7 import android.content.ComponentName;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.ServiceConnection;
11 import android.os.IBinder;
12 import android.os.Looper;
13 import android.os.RemoteException;
14 
15 import androidx.annotation.GuardedBy;
16 
17 import org.junit.Assert;
18 
19 import org.chromium.base.Log;
20 import org.chromium.base.ThreadUtils;
21 import org.chromium.net.X509Util;
22 import org.chromium.net.test.util.CertTestUtil;
23 
24 import java.io.File;
25 
26 /**
27  * A simple file server for java tests.
28  *
29  * An example use:
30  * <pre>
31  * EmbeddedTestServer s = EmbeddedTestServer.createAndStartServer(context);
32  *
33  * // serve requests...
34  * s.getURL("/foo/bar.txt");
35  *
36  * s.stopAndDestroyServer();
37  * </pre>
38  *
39  * Note that this runs net::test_server::EmbeddedTestServer in a service in a separate APK.
40  */
41 public class EmbeddedTestServer {
42     private static final String TAG = "TestServer";
43 
44     private static final String EMBEDDED_TEST_SERVER_SERVICE =
45             "org.chromium.net.test.EMBEDDED_TEST_SERVER_SERVICE";
46     private static final long SERVICE_CONNECTION_WAIT_INTERVAL_MS = 5000;
47 
48     @GuardedBy("mImplMonitor")
49     private IEmbeddedTestServerImpl mImpl;
50     private ServiceConnection mConn = new ServiceConnection() {
51         @Override
52         public void onServiceConnected(ComponentName name, IBinder service) {
53             synchronized (mImplMonitor) {
54                 mImpl = IEmbeddedTestServerImpl.Stub.asInterface(service);
55                 mImplMonitor.notify();
56             }
57         }
58 
59         @Override
60         public void onServiceDisconnected(ComponentName name) {
61             synchronized (mImplMonitor) {
62                 mImpl = null;
63                 mImplMonitor.notify();
64             }
65         }
66     };
67 
68     private Context mContext;
69     private final Object mImplMonitor = new Object();
70 
71     // Whether the server should use HTTP or HTTPS.
72     public enum ServerHTTPSSetting {
73         USE_HTTP,
74         USE_HTTPS,
75     }
76 
77     /**
78      * Exception class raised on failure in the EmbeddedTestServer.
79      */
80     public static final class EmbeddedTestServerFailure extends Error {
EmbeddedTestServerFailure(String errorDesc)81         public EmbeddedTestServerFailure(String errorDesc) {
82             super(errorDesc);
83         }
84 
EmbeddedTestServerFailure(String errorDesc, Throwable cause)85         public EmbeddedTestServerFailure(String errorDesc, Throwable cause) {
86             super(errorDesc, cause);
87         }
88     }
89 
90     /**
91      * Connection listener class, to be notified of new connections and sockets reads.
92      *
93      * Notifications are asynchronous and delivered to the UI thread.
94      */
95     public static class ConnectionListener {
96         private final IConnectionListener mListener = new IConnectionListener.Stub() {
97             @Override
98             public void acceptedSocket(final long socketId) {
99                 ThreadUtils.runOnUiThread(new Runnable() {
100                     @Override
101                     public void run() {
102                         ConnectionListener.this.acceptedSocket(socketId);
103                     }
104                 });
105             }
106 
107             @Override
108             public void readFromSocket(final long socketId) {
109                 ThreadUtils.runOnUiThread(new Runnable() {
110                     @Override
111                     public void run() {
112                         ConnectionListener.this.readFromSocket(socketId);
113                     }
114                 });
115             }
116         };
117 
118         /**
119          * A new socket connection has been opened on the server.
120          *
121          * @param socketId Socket unique identifier. Unique as long as the socket stays open.
122          */
acceptedSocket(long socketId)123         public void acceptedSocket(long socketId) {}
124 
125         /**
126          * Data  has been read from a socket.
127          *
128          * @param socketId Socket unique identifier. Unique as long as the socket stays open.
129          */
readFromSocket(long socketId)130         public void readFromSocket(long socketId) {}
131 
getListener()132         private IConnectionListener getListener() {
133             return mListener;
134         }
135     }
136 
137     /** Bind the service that will run the native server object.
138      *
139      *  @param context The context to use to bind the service. This will also be used to unbind
140      *          the service at server destruction time.
141      *  @param httpsSetting Whether the server should use HTTPS.
142      */
initializeNative(Context context, ServerHTTPSSetting httpsSetting)143     public void initializeNative(Context context, ServerHTTPSSetting httpsSetting) {
144         mContext = context;
145 
146         Intent intent = new Intent(EMBEDDED_TEST_SERVER_SERVICE);
147         setIntentClassName(intent);
148         if (!mContext.bindService(intent, mConn, Context.BIND_AUTO_CREATE)) {
149             throw new EmbeddedTestServerFailure(
150                     "Unable to bind to the EmbeddedTestServer service.");
151         }
152         synchronized (mImplMonitor) {
153             Log.i(TAG, "Waiting for EmbeddedTestServer service connection.");
154             while (mImpl == null) {
155                 try {
156                     mImplMonitor.wait(SERVICE_CONNECTION_WAIT_INTERVAL_MS);
157                 } catch (InterruptedException e) {
158                     // Ignore the InterruptedException. Rely on the outer while loop to re-run.
159                 }
160                 Log.i(TAG, "Still waiting for EmbeddedTestServer service connection.");
161             }
162             Log.i(TAG, "EmbeddedTestServer service connected.");
163             boolean initialized = false;
164             try {
165                 initialized = mImpl.initializeNative(httpsSetting == ServerHTTPSSetting.USE_HTTPS);
166             } catch (RemoteException e) {
167                 Log.e(TAG, "Failed to initialize native server.", e);
168                 initialized = false;
169             }
170 
171             if (!initialized) {
172                 throw new EmbeddedTestServerFailure("Failed to initialize native server.");
173             }
174 
175             if (httpsSetting == ServerHTTPSSetting.USE_HTTPS) {
176                 try {
177                     String rootCertPemPath = mImpl.getRootCertPemPath();
178                     X509Util.addTestRootCertificate(CertTestUtil.pemToDer(rootCertPemPath));
179                 } catch (Exception e) {
180                     throw new EmbeddedTestServerFailure(
181                             "Failed to install root certificate from native server.", e);
182                 }
183             }
184         }
185     }
186 
187     /** Set intent package and class name that will pass to the service.
188      *
189      *  @param intent The intent to use to pass into the service.
190      */
setIntentClassName(Intent intent)191     protected void setIntentClassName(Intent intent) {
192         intent.setClassName(
193                 "org.chromium.net.test.support", "org.chromium.net.test.EmbeddedTestServerService");
194     }
195 
196     /** Add the default handlers and serve files from the provided directory relative to the
197      *  external storage directory.
198      *
199      *  @param directory The directory from which files should be served relative to the external
200      *      storage directory.
201      */
addDefaultHandlers(File directory)202     public void addDefaultHandlers(File directory) {
203         addDefaultHandlers(directory.getPath());
204     }
205 
206     /** Add the default handlers and serve files from the provided directory relative to the
207      *  external storage directory.
208      *
209      *  @param directoryPath The path of the directory from which files should be served relative
210      *      to the external storage directory.
211      */
addDefaultHandlers(String directoryPath)212     public void addDefaultHandlers(String directoryPath) {
213         try {
214             synchronized (mImplMonitor) {
215                 checkServiceLocked();
216                 mImpl.addDefaultHandlers(directoryPath);
217             }
218         } catch (RemoteException e) {
219             throw new EmbeddedTestServerFailure(
220                     "Failed to add default handlers and start serving files from " + directoryPath
221                     + ": " + e.toString());
222         }
223     }
224 
225     /** Configure the server to use a particular type of SSL certificate.
226      *
227      * @param serverCertificate The type of certificate the server should use.
228      */
setSSLConfig(@erverCertificate int serverCertificate)229     public void setSSLConfig(@ServerCertificate int serverCertificate) {
230         try {
231             synchronized (mImplMonitor) {
232                 checkServiceLocked();
233                 mImpl.setSSLConfig(serverCertificate);
234             }
235         } catch (RemoteException e) {
236             throw new EmbeddedTestServerFailure(
237                     "Failed to set server certificate: " + e.toString());
238         }
239     }
240 
241     /** Serve files from the provided directory.
242      *
243      *  @param directory The directory from which files should be served.
244      */
serveFilesFromDirectory(File directory)245     public void serveFilesFromDirectory(File directory) {
246         serveFilesFromDirectory(directory.getPath());
247     }
248 
249     /** Serve files from the provided directory.
250      *
251      *  @param directoryPath The path of the directory from which files should be served.
252      */
serveFilesFromDirectory(String directoryPath)253     public void serveFilesFromDirectory(String directoryPath) {
254         try {
255             synchronized (mImplMonitor) {
256                 checkServiceLocked();
257                 mImpl.serveFilesFromDirectory(directoryPath);
258             }
259         } catch (RemoteException e) {
260             throw new EmbeddedTestServerFailure(
261                     "Failed to start serving files from " + directoryPath + ": " + e.toString());
262         }
263     }
264 
265     /**
266      * Sets a connection listener. Must be called after the server has been initialized, but
267      * before calling {@link start()}.
268      *
269      * @param listener The listener to set.
270      */
setConnectionListener(ConnectionListener listener)271     public void setConnectionListener(ConnectionListener listener) {
272         try {
273             synchronized (mImplMonitor) {
274                 checkServiceLocked();
275                 mImpl.setConnectionListener(listener.getListener());
276             }
277         } catch (RemoteException e) {
278             throw new EmbeddedTestServerFailure("Cannot set the listener");
279         }
280     }
281 
282     @GuardedBy("mImplMonitor")
checkServiceLocked()283     private void checkServiceLocked() {
284         if (mImpl == null) {
285             throw new EmbeddedTestServerFailure("Service disconnected.");
286         }
287     }
288 
289     /** Starts the server with an automatically selected port.
290      *
291      *  Note that this should be called after handlers are set up, including any relevant calls
292      *  serveFilesFromDirectory.
293      *
294      *  @return Whether the server was successfully initialized.
295      */
start()296     public boolean start() {
297         return start(0);
298     }
299 
300     /** Starts the server with the specified port.
301      *
302      *  Note that this should be called after handlers are set up, including any relevant calls
303      *  serveFilesFromDirectory.
304      *
305      *  @param port The port to use for the server, 0 to auto-select an unused port.
306      *
307      *  @return Whether the server was successfully initialized.
308      */
start(int port)309     public boolean start(int port) {
310         try {
311             synchronized (mImplMonitor) {
312                 checkServiceLocked();
313                 return mImpl.start(port);
314             }
315         } catch (RemoteException e) {
316             throw new EmbeddedTestServerFailure("Failed to start server.", e);
317         }
318     }
319 
320     /** Create and initialize a server with the default handlers.
321      *
322      *  This handles native object initialization, server configuration, and server initialization.
323      *  On returning, the server is ready for use.
324      *
325      *  @param context The context in which the server will run.
326      *  @return The created server.
327      */
createAndStartServer(Context context)328     public static EmbeddedTestServer createAndStartServer(Context context) {
329         return createAndStartServerWithPort(context, 0);
330     }
331 
332     /** Create and initialize a server with the default handlers and specified port.
333      *
334      *  This handles native object initialization, server configuration, and server initialization.
335      *  On returning, the server is ready for use.
336      *
337      *  @param context The context in which the server will run.
338      *  @param port The port to use for the server, 0 to auto-select an unused port.
339      *  @return The created server.
340      */
createAndStartServerWithPort(Context context, int port)341     public static EmbeddedTestServer createAndStartServerWithPort(Context context, int port) {
342         Assert.assertNotEquals("EmbeddedTestServer should not be created on UiThread, "
343                 + "the instantiation will hang forever waiting for tasks to post to UI thread",
344                 Looper.getMainLooper(), Looper.myLooper());
345         EmbeddedTestServer server = new EmbeddedTestServer();
346         return initializeAndStartServer(server, context, port);
347     }
348 
349     /** Create and initialize an HTTPS server with the default handlers.
350      *
351      *  This handles native object initialization, server configuration, and server initialization.
352      *  On returning, the server is ready for use.
353      *
354      *  @param context The context in which the server will run.
355      *  @param serverCertificate The certificate option that the server will use.
356      *  @return The created server.
357      */
createAndStartHTTPSServer( Context context, @ServerCertificate int serverCertificate)358     public static EmbeddedTestServer createAndStartHTTPSServer(
359             Context context, @ServerCertificate int serverCertificate) {
360         return createAndStartHTTPSServerWithPort(context, serverCertificate, 0 /* port */);
361     }
362 
363     /** Create and initialize an HTTPS server with the default handlers and specified port.
364      *
365      *  This handles native object initialization, server configuration, and server initialization.
366      *  On returning, the server is ready for use.
367      *
368      *  @param context The context in which the server will run.
369      *  @param serverCertificate The certificate option that the server will use.
370      *  @param port The port to use for the server, 0 to auto-select an unused port.
371      *  @return The created server.
372      */
createAndStartHTTPSServerWithPort( Context context, @ServerCertificate int serverCertificate, int port)373     public static EmbeddedTestServer createAndStartHTTPSServerWithPort(
374             Context context, @ServerCertificate int serverCertificate, int port) {
375         Assert.assertNotEquals("EmbeddedTestServer should not be created on UiThread, "
376                         + "the instantiation will hang forever waiting for tasks"
377                         + " to post to UI thread",
378                 Looper.getMainLooper(), Looper.myLooper());
379         EmbeddedTestServer server = new EmbeddedTestServer();
380         return initializeAndStartHTTPSServer(server, context, serverCertificate, port);
381     }
382 
383     /** Initialize a server with the default handlers.
384      *
385      *  This handles native object initialization, server configuration, and server initialization.
386      *  On returning, the server is ready for use.
387      *
388      *  @param server The server instance that will be initialized.
389      *  @param context The context in which the server will run.
390      *  @param port The port to use for the server, 0 to auto-select an unused port.
391      *  @return The created server.
392      */
initializeAndStartServer( T server, Context context, int port)393     public static <T extends EmbeddedTestServer> T initializeAndStartServer(
394             T server, Context context, int port) {
395         server.initializeNative(context, ServerHTTPSSetting.USE_HTTP);
396         server.addDefaultHandlers("");
397         if (!server.start(port)) {
398             throw new EmbeddedTestServerFailure("Failed to start serving using default handlers.");
399         }
400         return server;
401     }
402 
403     /** Initialize a server with the default handlers that uses HTTPS with the given certificate
404      * option.
405      *
406      *  This handles native object initialization, server configuration, and server initialization.
407      *  On returning, the server is ready for use.
408      *
409      *  @param server The server instance that will be initialized.
410      *  @param context The context in which the server will run.
411      *  @param serverCertificate The certificate option that the server will use.
412      *  @param port The port to use for the server.
413      *  @return The created server.
414      */
initializeAndStartHTTPSServer( T server, Context context, @ServerCertificate int serverCertificate, int port)415     public static <T extends EmbeddedTestServer> T initializeAndStartHTTPSServer(
416             T server, Context context, @ServerCertificate int serverCertificate, int port) {
417         server.initializeNative(context, ServerHTTPSSetting.USE_HTTPS);
418         server.addDefaultHandlers("");
419         server.setSSLConfig(serverCertificate);
420         if (!server.start(port)) {
421             throw new EmbeddedTestServerFailure("Failed to start serving using default handlers.");
422         }
423         return server;
424     }
425 
426     /** Get the full URL for the given relative URL.
427      *
428      *  @param relativeUrl The relative URL for which a full URL will be obtained.
429      *  @return The URL as a String.
430      */
getURL(String relativeUrl)431     public String getURL(String relativeUrl) {
432         try {
433             synchronized (mImplMonitor) {
434                 checkServiceLocked();
435                 return mImpl.getURL(relativeUrl);
436             }
437         } catch (RemoteException e) {
438             throw new EmbeddedTestServerFailure("Failed to get URL for " + relativeUrl, e);
439         }
440     }
441 
442     /** Get the full URL for the given relative URL. Similar to the above method but uses the given
443      *  hostname instead of 127.0.0.1. The hostname should be resolved to 127.0.0.1.
444      *
445      *  @param hostName The host name which should be used.
446      *  @param relativeUrl The relative URL for which a full URL should be returned.
447      *  @return The URL as a String.
448      */
getURLWithHostName(String hostname, String relativeUrl)449     public String getURLWithHostName(String hostname, String relativeUrl) {
450         try {
451             synchronized (mImplMonitor) {
452                 checkServiceLocked();
453                 return mImpl.getURLWithHostName(hostname, relativeUrl);
454             }
455         } catch (RemoteException e) {
456             throw new EmbeddedTestServerFailure(
457                     "Failed to get URL for " + hostname + " and " + relativeUrl, e);
458         }
459     }
460 
461     /** Get the full URLs for the given relative URLs.
462      *
463      *  @see #getURL(String)
464      *
465      *  @param relativeUrls The relative URLs for which full URLs will be obtained.
466      *  @return The URLs as a String array.
467      */
getURLs(String... relativeUrls)468     public String[] getURLs(String... relativeUrls) {
469         String[] absoluteUrls = new String[relativeUrls.length];
470 
471         for (int i = 0; i < relativeUrls.length; ++i) absoluteUrls[i] = getURL(relativeUrls[i]);
472 
473         return absoluteUrls;
474     }
475 
476     /** Shutdown the server.
477      *
478      *  @return Whether the server was successfully shut down.
479      */
shutdownAndWaitUntilComplete()480     public boolean shutdownAndWaitUntilComplete() {
481         try {
482             synchronized (mImplMonitor) {
483                 checkServiceLocked();
484                 return mImpl.shutdownAndWaitUntilComplete();
485             }
486         } catch (RemoteException e) {
487             throw new EmbeddedTestServerFailure("Failed to shut down.", e);
488         }
489     }
490 
491     /** Destroy the native EmbeddedTestServer object. */
destroy()492     public void destroy() {
493         try {
494             synchronized (mImplMonitor) {
495                 checkServiceLocked();
496                 mImpl.destroy();
497                 mImpl = null;
498             }
499         } catch (RemoteException e) {
500             throw new EmbeddedTestServerFailure("Failed to destroy native server.", e);
501         } finally {
502             mContext.unbindService(mConn);
503         }
504     }
505 
506     /** Stop and destroy the server.
507      *
508      *  This handles stopping the server and destroying the native object.
509      */
stopAndDestroyServer()510     public void stopAndDestroyServer() {
511         if (!shutdownAndWaitUntilComplete()) {
512             throw new EmbeddedTestServerFailure("Failed to stop server.");
513         }
514         destroy();
515     }
516 
517     /** Get the path of the PEM file of the root cert. */
getRootCertPemPath()518     public String getRootCertPemPath() {
519         try {
520             synchronized (mImplMonitor) {
521                 checkServiceLocked();
522                 return mImpl.getRootCertPemPath();
523             }
524         } catch (RemoteException e) {
525             throw new EmbeddedTestServerFailure("Failed to get root cert's path", e);
526         }
527     }
528 }
529