| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.net.test; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Environment; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| |
| import org.junit.Assert; |
| |
| import org.chromium.base.Log; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.net.X509Util; |
| import org.chromium.net.test.util.CertTestUtil; |
| |
| import java.io.File; |
| |
| /** A simple file server for java tests. |
| * |
| * An example use: |
| * EmbeddedTestServer s = EmbeddedTestServer.createAndStartServer(context); |
| * |
| * // serve requests... |
| * s.getURL("/foo/bar.txt"); |
| * |
| * s.stopAndDestroyServer(); |
| * |
| * Note that this runs net::test_server::EmbeddedTestServer in a service in a separate APK. |
| */ |
| public class EmbeddedTestServer { |
| private static final String TAG = "cr_TestServer"; |
| |
| private static final String EMBEDDED_TEST_SERVER_SERVICE = |
| "org.chromium.net.test.EMBEDDED_TEST_SERVER_SERVICE"; |
| private static final long SERVICE_CONNECTION_WAIT_INTERVAL_MS = 5000; |
| |
| private IEmbeddedTestServerImpl mImpl; |
| private ServiceConnection mConn = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| synchronized (mImplMonitor) { |
| mImpl = IEmbeddedTestServerImpl.Stub.asInterface(service); |
| mImplMonitor.notify(); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| synchronized (mImplMonitor) { |
| mImpl = null; |
| mImplMonitor.notify(); |
| } |
| } |
| }; |
| |
| private Context mContext; |
| private final Object mImplMonitor = new Object(); |
| |
| // Whether the server should use HTTP or HTTPS. |
| public enum ServerHTTPSSetting { |
| USE_HTTP, |
| USE_HTTPS, |
| } |
| |
| /** |
| * Exception class raised on failure in the EmbeddedTestServer. |
| */ |
| public static final class EmbeddedTestServerFailure extends Error { |
| public EmbeddedTestServerFailure(String errorDesc) { |
| super(errorDesc); |
| } |
| |
| public EmbeddedTestServerFailure(String errorDesc, Throwable cause) { |
| super(errorDesc, cause); |
| } |
| } |
| |
| /** |
| * Connection listener class, to be notified of new connections and sockets reads. |
| * |
| * Notifications are asynchronous and delivered to the UI thread. |
| */ |
| public static class ConnectionListener { |
| private final IConnectionListener mListener = new IConnectionListener.Stub() { |
| @Override |
| public void acceptedSocket(final long socketId) { |
| ThreadUtils.runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| ConnectionListener.this.acceptedSocket(socketId); |
| } |
| }); |
| } |
| |
| @Override |
| public void readFromSocket(final long socketId) { |
| ThreadUtils.runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| ConnectionListener.this.readFromSocket(socketId); |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * A new socket connection has been opened on the server. |
| * |
| * @param socketId Socket unique identifier. Unique as long as the socket stays open. |
| */ |
| public void acceptedSocket(long socketId) {} |
| |
| /** |
| * Data has been read from a socket. |
| * |
| * @param socketId Socket unique identifier. Unique as long as the socket stays open. |
| */ |
| public void readFromSocket(long socketId) {} |
| |
| private IConnectionListener getListener() { |
| return mListener; |
| } |
| } |
| |
| /** Bind the service that will run the native server object. |
| * |
| * @param context The context to use to bind the service. This will also be used to unbind |
| * the service at server destruction time. |
| * @param httpsSetting Whether the server should use HTTPS. |
| */ |
| public void initializeNative(Context context, ServerHTTPSSetting httpsSetting) |
| throws InterruptedException { |
| mContext = context; |
| |
| Intent intent = new Intent(EMBEDDED_TEST_SERVER_SERVICE); |
| setIntentClassName(intent); |
| if (!mContext.bindService(intent, mConn, Context.BIND_AUTO_CREATE)) { |
| throw new EmbeddedTestServerFailure( |
| "Unable to bind to the EmbeddedTestServer service."); |
| } |
| synchronized (mImplMonitor) { |
| Log.i(TAG, "Waiting for EmbeddedTestServer service connection."); |
| while (mImpl == null) { |
| mImplMonitor.wait(SERVICE_CONNECTION_WAIT_INTERVAL_MS); |
| Log.i(TAG, "Still waiting for EmbeddedTestServer service connection."); |
| } |
| Log.i(TAG, "EmbeddedTestServer service connected."); |
| boolean initialized = false; |
| try { |
| initialized = mImpl.initializeNative(httpsSetting == ServerHTTPSSetting.USE_HTTPS); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to initialize native server.", e); |
| initialized = false; |
| } |
| |
| if (!initialized) { |
| throw new EmbeddedTestServerFailure("Failed to initialize native server."); |
| } |
| |
| if (httpsSetting == ServerHTTPSSetting.USE_HTTPS) { |
| try { |
| String rootCertPemPath = mImpl.getRootCertPemPath(); |
| X509Util.addTestRootCertificate(CertTestUtil.pemToDer(rootCertPemPath)); |
| } catch (Exception e) { |
| throw new EmbeddedTestServerFailure( |
| "Failed to install root certificate from native server.", e); |
| } |
| } |
| } |
| } |
| |
| /** Set intent package and class name that will pass to the service. |
| * |
| * @param intent The intent to use to pass into the service. |
| */ |
| protected void setIntentClassName(Intent intent) { |
| intent.setClassName( |
| "org.chromium.net.test.support", "org.chromium.net.test.EmbeddedTestServerService"); |
| } |
| |
| /** Add the default handlers and serve files from the provided directory relative to the |
| * external storage directory. |
| * |
| * @param directory The directory from which files should be served relative to the external |
| * storage directory. |
| */ |
| public void addDefaultHandlers(File directory) { |
| addDefaultHandlers(directory.getPath()); |
| } |
| |
| /** Add the default handlers and serve files from the provided directory relative to the |
| * external storage directory. |
| * |
| * @param directoryPath The path of the directory from which files should be served relative |
| * to the external storage directory. |
| */ |
| public void addDefaultHandlers(String directoryPath) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| mImpl.addDefaultHandlers(directoryPath); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure( |
| "Failed to add default handlers and start serving files from " + directoryPath |
| + ": " + e.toString()); |
| } |
| } |
| |
| /** Configure the server to use a particular type of SSL certificate. |
| * |
| * @param serverCertificate The type of certificate the server should use. |
| */ |
| public void setSSLConfig(int serverCertificate) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| mImpl.setSSLConfig(serverCertificate); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure( |
| "Failed to set server certificate: " + e.toString()); |
| } |
| } |
| |
| /** Serve files from the provided directory. |
| * |
| * @param directory The directory from which files should be served. |
| */ |
| public void serveFilesFromDirectory(File directory) { |
| serveFilesFromDirectory(directory.getPath()); |
| } |
| |
| /** Serve files from the provided directory. |
| * |
| * @param directoryPath The path of the directory from which files should be served. |
| */ |
| public void serveFilesFromDirectory(String directoryPath) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| mImpl.serveFilesFromDirectory(directoryPath); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure( |
| "Failed to start serving files from " + directoryPath + ": " + e.toString()); |
| } |
| } |
| |
| /** |
| * Sets a connection listener. Must be called after the server has been initialized, but |
| * before calling {@link start()}. |
| * |
| * @param listener The listener to set. |
| */ |
| public void setConnectionListener(ConnectionListener listener) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| mImpl.setConnectionListener(listener.getListener()); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure("Cannot set the listener"); |
| } |
| } |
| |
| private void checkServiceLocked() { |
| if (mImpl == null) { |
| throw new EmbeddedTestServerFailure("Service disconnected."); |
| } |
| } |
| |
| /** Starts the server with an automatically selected port. |
| * |
| * Note that this should be called after handlers are set up, including any relevant calls |
| * serveFilesFromDirectory. |
| * |
| * @return Whether the server was successfully initialized. |
| */ |
| public boolean start() { |
| return start(0); |
| } |
| |
| /** Starts the server with the specified port. |
| * |
| * Note that this should be called after handlers are set up, including any relevant calls |
| * serveFilesFromDirectory. |
| * |
| * @param port The port to use for the server, 0 to auto-select an unused port. |
| * |
| * @return Whether the server was successfully initialized. |
| */ |
| public boolean start(int port) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| return mImpl.start(port); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure("Failed to start server.", e); |
| } |
| } |
| |
| /** Create and initialize a server that serves files from the provided directory. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param context The context in which the server will run. |
| * @param directory The directory from which files should be served. This must be |
| * Environment.getExternalStorageDirectory(). |
| * @return The created server. |
| */ |
| public static EmbeddedTestServer createAndStartFileServer(Context context, File directory) |
| throws InterruptedException { |
| // TODO(jbudorick): Update all callers to use createAndStartServer() directly. |
| if (!directory.equals(Environment.getExternalStorageDirectory())) { |
| throw new IllegalArgumentException("Expected directory to be ExternalStorageDirectory"); |
| } |
| return createAndStartServer(context); |
| } |
| |
| /** Create and initialize a server with the default handlers. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param context The context in which the server will run. |
| * @return The created server. |
| */ |
| public static EmbeddedTestServer createAndStartDefaultServer(Context context) |
| throws InterruptedException { |
| // TODO(pkotwicz): Update all callers to use createAndStartServer() directly. |
| return createAndStartServer(context); |
| } |
| |
| /** Create and initialize a server with the default handlers. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param context The context in which the server will run. |
| * @return The created server. |
| */ |
| public static EmbeddedTestServer createAndStartServer(Context context) |
| throws InterruptedException { |
| return createAndStartServerWithPort(context, 0); |
| } |
| |
| /** Create and initialize a server with the default handlers and specified port. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param context The context in which the server will run. |
| * @param port The port to use for the server, 0 to auto-select an unused port. |
| * @return The created server. |
| */ |
| public static EmbeddedTestServer createAndStartServerWithPort(Context context, int port) |
| throws InterruptedException { |
| Assert.assertNotEquals("EmbeddedTestServer should not be created on UiThread, " |
| + "the instantiation will hang forever waiting for tasks to post to UI thread", |
| Looper.getMainLooper(), Looper.myLooper()); |
| EmbeddedTestServer server = new EmbeddedTestServer(); |
| return initializeAndStartServer(server, context, port); |
| } |
| |
| /** Create and initialize an HTTPS server with the default handlers. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param context The context in which the server will run. |
| * @param serverCertificate The certificate option that the server will use. |
| * @return The created server. |
| */ |
| public static EmbeddedTestServer createAndStartHTTPSServer( |
| Context context, int serverCertificate) throws InterruptedException { |
| return createAndStartHTTPSServerWithPort(context, serverCertificate, 0 /* port */); |
| } |
| |
| /** Create and initialize an HTTPS server with the default handlers and specified port. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param context The context in which the server will run. |
| * @param serverCertificate The certificate option that the server will use. |
| * @param port The port to use for the server, 0 to auto-select an unused port. |
| * @return The created server. |
| */ |
| public static EmbeddedTestServer createAndStartHTTPSServerWithPort( |
| Context context, int serverCertificate, int port) throws InterruptedException { |
| Assert.assertNotEquals("EmbeddedTestServer should not be created on UiThread, " |
| + "the instantiation will hang forever waiting for tasks" |
| + " to post to UI thread", |
| Looper.getMainLooper(), Looper.myLooper()); |
| EmbeddedTestServer server = new EmbeddedTestServer(); |
| return initializeAndStartHTTPSServer(server, context, serverCertificate, port); |
| } |
| |
| /** Initialize a server with the default handlers. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param server The server instance that will be initialized. |
| * @param context The context in which the server will run. |
| * @param port The port to use for the server, 0 to auto-select an unused port. |
| * @return The created server. |
| */ |
| public static <T extends EmbeddedTestServer> T initializeAndStartServer( |
| T server, Context context, int port) throws InterruptedException { |
| server.initializeNative(context, ServerHTTPSSetting.USE_HTTP); |
| server.addDefaultHandlers(""); |
| if (!server.start(port)) { |
| throw new EmbeddedTestServerFailure("Failed to start serving using default handlers."); |
| } |
| return server; |
| } |
| |
| /** Initialize a server with the default handlers that uses HTTPS with the given certificate |
| * option. |
| * |
| * This handles native object initialization, server configuration, and server initialization. |
| * On returning, the server is ready for use. |
| * |
| * @param server The server instance that will be initialized. |
| * @param context The context in which the server will run. |
| * @param serverCertificate The certificate option that the server will use. |
| * @param port The port to use for the server. |
| * @return The created server. |
| */ |
| public static <T extends EmbeddedTestServer> T initializeAndStartHTTPSServer(T server, |
| Context context, int serverCertificate, int port) throws InterruptedException { |
| server.initializeNative(context, ServerHTTPSSetting.USE_HTTPS); |
| server.addDefaultHandlers(""); |
| server.setSSLConfig(serverCertificate); |
| if (!server.start(port)) { |
| throw new EmbeddedTestServerFailure("Failed to start serving using default handlers."); |
| } |
| return server; |
| } |
| |
| /** Get the full URL for the given relative URL. |
| * |
| * @param relativeUrl The relative URL for which a full URL will be obtained. |
| * @return The URL as a String. |
| */ |
| public String getURL(String relativeUrl) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| return mImpl.getURL(relativeUrl); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure("Failed to get URL for " + relativeUrl, e); |
| } |
| } |
| |
| /** Get the full URL for the given relative URL. Similar to the above method but uses the given |
| * hostname instead of 127.0.0.1. The hostname should be resolved to 127.0.0.1. |
| * |
| * @param hostName The host name which should be used. |
| * @param relativeUrl The relative URL for which a full URL should be returned. |
| * @return The URL as a String. |
| */ |
| public String getURLWithHostName(String hostname, String relativeUrl) { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| return mImpl.getURLWithHostName(hostname, relativeUrl); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure( |
| "Failed to get URL for " + hostname + " and " + relativeUrl, e); |
| } |
| } |
| |
| /** Get the full URLs for the given relative URLs. |
| * |
| * @see #getURL(String) |
| * |
| * @param relativeUrls The relative URLs for which full URLs will be obtained. |
| * @return The URLs as a String array. |
| */ |
| public String[] getURLs(String... relativeUrls) { |
| String[] absoluteUrls = new String[relativeUrls.length]; |
| |
| for (int i = 0; i < relativeUrls.length; ++i) absoluteUrls[i] = getURL(relativeUrls[i]); |
| |
| return absoluteUrls; |
| } |
| |
| /** Shutdown the server. |
| * |
| * @return Whether the server was successfully shut down. |
| */ |
| public boolean shutdownAndWaitUntilComplete() { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| return mImpl.shutdownAndWaitUntilComplete(); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure("Failed to shut down.", e); |
| } |
| } |
| |
| /** Destroy the native EmbeddedTestServer object. */ |
| public void destroy() { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| mImpl.destroy(); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure("Failed to destroy native server.", e); |
| } finally { |
| mContext.unbindService(mConn); |
| } |
| } |
| |
| /** Stop and destroy the server. |
| * |
| * This handles stopping the server and destroying the native object. |
| */ |
| public void stopAndDestroyServer() { |
| if (!shutdownAndWaitUntilComplete()) { |
| throw new EmbeddedTestServerFailure("Failed to stop server."); |
| } |
| destroy(); |
| } |
| |
| /** Get the path of the PEM file of the root cert. */ |
| public String getRootCertPemPath() { |
| try { |
| synchronized (mImplMonitor) { |
| checkServiceLocked(); |
| return mImpl.getRootCertPemPath(); |
| } |
| } catch (RemoteException e) { |
| throw new EmbeddedTestServerFailure("Failed to get root cert's path", e); |
| } |
| } |
| } |