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