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