xref: /JGit/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java (revision 0f442d70836ee292ed916605448f806cc7d1fe78)
151e2646dSJens Baumgart /*
25c5f7c6bSMatthias Sohn  * Copyright (C) 2010, 2017 Google Inc. and others
351e2646dSJens Baumgart  *
45c5f7c6bSMatthias Sohn  * This program and the accompanying materials are made available under the
55c5f7c6bSMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
65c5f7c6bSMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
751e2646dSJens Baumgart  *
85c5f7c6bSMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
951e2646dSJens Baumgart  */
1051e2646dSJens Baumgart 
1151e2646dSJens Baumgart package org.eclipse.jgit.junit.http;
1251e2646dSJens Baumgart 
13d9e07a57SRobin Rosenberg import static org.junit.Assert.assertFalse;
14d9e07a57SRobin Rosenberg import static org.junit.Assert.assertTrue;
15d9e07a57SRobin Rosenberg 
16e17bfc96SThomas Wolf import java.io.File;
17e17bfc96SThomas Wolf import java.io.IOException;
1851e2646dSJens Baumgart import java.net.InetAddress;
1951e2646dSJens Baumgart import java.net.URI;
2051e2646dSJens Baumgart import java.net.URISyntaxException;
2151e2646dSJens Baumgart import java.net.UnknownHostException;
22e17bfc96SThomas Wolf import java.nio.file.Files;
2351e2646dSJens Baumgart import java.util.ArrayList;
2451e2646dSJens Baumgart import java.util.List;
257ac1bfc8SThomas Wolf import java.util.Locale;
26*0f442d70SThomas Wolf import java.util.Map;
2733bbbc3cSMat Booth import java.util.concurrent.ConcurrentHashMap;
2851e2646dSJens Baumgart 
29e17bfc96SThomas Wolf import org.eclipse.jetty.http.HttpVersion;
3033bbbc3cSMat Booth import org.eclipse.jetty.security.AbstractLoginService;
3151e2646dSJens Baumgart import org.eclipse.jetty.security.Authenticator;
3251e2646dSJens Baumgart import org.eclipse.jetty.security.ConstraintMapping;
3351e2646dSJens Baumgart import org.eclipse.jetty.security.ConstraintSecurityHandler;
3451e2646dSJens Baumgart import org.eclipse.jetty.security.authentication.BasicAuthenticator;
3551e2646dSJens Baumgart import org.eclipse.jetty.server.Connector;
36a24b7c3cSMatthias Sohn import org.eclipse.jetty.server.HttpConfiguration;
37a24b7c3cSMatthias Sohn import org.eclipse.jetty.server.HttpConnectionFactory;
3851e2646dSJens Baumgart import org.eclipse.jetty.server.Server;
39a24b7c3cSMatthias Sohn import org.eclipse.jetty.server.ServerConnector;
40e17bfc96SThomas Wolf import org.eclipse.jetty.server.SslConnectionFactory;
4151e2646dSJens Baumgart import org.eclipse.jetty.server.handler.ContextHandlerCollection;
4251e2646dSJens Baumgart import org.eclipse.jetty.servlet.ServletContextHandler;
4324a0f47eSMatthias Sohn import org.eclipse.jetty.util.security.Constraint;
44da6fa14fSDavid Pursehouse import org.eclipse.jetty.util.security.Password;
45e17bfc96SThomas Wolf import org.eclipse.jetty.util.ssl.SslContextFactory;
4651e2646dSJens Baumgart import org.eclipse.jgit.transport.URIish;
4751e2646dSJens Baumgart 
4851e2646dSJens Baumgart /**
4951e2646dSJens Baumgart  * Tiny web application server for unit testing.
5051e2646dSJens Baumgart  * <p>
5151e2646dSJens Baumgart  * Tests should start the server in their {@code setUp()} method and stop the
5251e2646dSJens Baumgart  * server in their {@code tearDown()} method. Only while started the server's
5351e2646dSJens Baumgart  * URL and/or port number can be obtained.
5451e2646dSJens Baumgart  */
5551e2646dSJens Baumgart public class AppServer {
5651e2646dSJens Baumgart 	/** Realm name for the secure access areas. */
5751e2646dSJens Baumgart 	public static final String realm = "Secure Area";
5851e2646dSJens Baumgart 
5951e2646dSJens Baumgart 	/** Username for secured access areas. */
6051e2646dSJens Baumgart 	public static final String username = "agitter";
6151e2646dSJens Baumgart 
6251e2646dSJens Baumgart 	/** Password for {@link #username} in secured access areas. */
6351e2646dSJens Baumgart 	public static final String password = "letmein";
6451e2646dSJens Baumgart 
65e17bfc96SThomas Wolf 	/** SSL keystore password; must have at least 6 characters. */
66e17bfc96SThomas Wolf 	private static final String keyPassword = "mykeys";
67e17bfc96SThomas Wolf 
687ac1bfc8SThomas Wolf 	/** Role for authentication. */
697ac1bfc8SThomas Wolf 	private static final String authRole = "can-access";
707ac1bfc8SThomas Wolf 
7151e2646dSJens Baumgart 	static {
7251e2646dSJens Baumgart 		// Install a logger that throws warning messages.
7351e2646dSJens Baumgart 		//
7451e2646dSJens Baumgart 		final String prop = "org.eclipse.jetty.util.log.class";
System.setProperty(prop, RecordingLogger.class.getName())7551e2646dSJens Baumgart 		System.setProperty(prop, RecordingLogger.class.getName());
7651e2646dSJens Baumgart 	}
7751e2646dSJens Baumgart 
7851e2646dSJens Baumgart 	private final Server server;
7951e2646dSJens Baumgart 
80e17bfc96SThomas Wolf 	private final HttpConfiguration config;
81e17bfc96SThomas Wolf 
82a24b7c3cSMatthias Sohn 	private final ServerConnector connector;
8351e2646dSJens Baumgart 
84e17bfc96SThomas Wolf 	private final HttpConfiguration secureConfig;
85e17bfc96SThomas Wolf 
86e17bfc96SThomas Wolf 	private final ServerConnector secureConnector;
87e17bfc96SThomas Wolf 
8851e2646dSJens Baumgart 	private final ContextHandlerCollection contexts;
8951e2646dSJens Baumgart 
9051e2646dSJens Baumgart 	private final TestRequestLog log;
9151e2646dSJens Baumgart 
92e17bfc96SThomas Wolf 	private List<File> filesToDelete = new ArrayList<>();
93e17bfc96SThomas Wolf 
940b131b73SMatthias Sohn 	/**
950b131b73SMatthias Sohn 	 * Constructor for <code>AppServer</code>.
960b131b73SMatthias Sohn 	 */
AppServer()9751e2646dSJens Baumgart 	public AppServer() {
98e17bfc96SThomas Wolf 		this(0, -1);
993bae524fSMatthias Sohn 	}
1003bae524fSMatthias Sohn 
1013bae524fSMatthias Sohn 	/**
1020b131b73SMatthias Sohn 	 * Constructor for <code>AppServer</code>.
1030b131b73SMatthias Sohn 	 *
1043bae524fSMatthias Sohn 	 * @param port
105e17bfc96SThomas Wolf 	 *            the http port number; may be zero to allocate a port
106e17bfc96SThomas Wolf 	 *            dynamically
1073bae524fSMatthias Sohn 	 * @since 4.2
1083bae524fSMatthias Sohn 	 */
AppServer(int port)1093bae524fSMatthias Sohn 	public AppServer(int port) {
110e17bfc96SThomas Wolf 		this(port, -1);
111e17bfc96SThomas Wolf 	}
112e17bfc96SThomas Wolf 
113e17bfc96SThomas Wolf 	/**
1140b131b73SMatthias Sohn 	 * Constructor for <code>AppServer</code>.
1150b131b73SMatthias Sohn 	 *
116e17bfc96SThomas Wolf 	 * @param port
1177ac1bfc8SThomas Wolf 	 *            for http, may be zero to allocate a port dynamically
118e17bfc96SThomas Wolf 	 * @param sslPort
119e17bfc96SThomas Wolf 	 *            for https,may be zero to allocate a port dynamically. If
1207ac1bfc8SThomas Wolf 	 *            negative, the server will be set up without https support.
121e17bfc96SThomas Wolf 	 * @since 4.9
122e17bfc96SThomas Wolf 	 */
AppServer(int port, int sslPort)123e17bfc96SThomas Wolf 	public AppServer(int port, int sslPort) {
124a24b7c3cSMatthias Sohn 		server = new Server();
125a24b7c3cSMatthias Sohn 
126e17bfc96SThomas Wolf 		config = new HttpConfiguration();
127e17bfc96SThomas Wolf 		config.setSecureScheme("https");
128e17bfc96SThomas Wolf 		config.setSecurePort(0);
129e17bfc96SThomas Wolf 		config.setOutputBufferSize(32768);
130a24b7c3cSMatthias Sohn 
131a24b7c3cSMatthias Sohn 		connector = new ServerConnector(server,
132e17bfc96SThomas Wolf 				new HttpConnectionFactory(config));
1333bae524fSMatthias Sohn 		connector.setPort(port);
134e17bfc96SThomas Wolf 		String ip;
135e17bfc96SThomas Wolf 		String hostName;
13651e2646dSJens Baumgart 		try {
13751e2646dSJens Baumgart 			final InetAddress me = InetAddress.getByName("localhost");
138e17bfc96SThomas Wolf 			ip = me.getHostAddress();
139e17bfc96SThomas Wolf 			connector.setHost(ip);
140e17bfc96SThomas Wolf 			hostName = InetAddress.getLocalHost().getCanonicalHostName();
14151e2646dSJens Baumgart 		} catch (UnknownHostException e) {
14251e2646dSJens Baumgart 			throw new RuntimeException("Cannot find localhost", e);
14351e2646dSJens Baumgart 		}
14451e2646dSJens Baumgart 
145e17bfc96SThomas Wolf 		if (sslPort >= 0) {
146e17bfc96SThomas Wolf 			SslContextFactory sslContextFactory = createTestSslContextFactory(
147e17bfc96SThomas Wolf 					hostName);
148e17bfc96SThomas Wolf 			secureConfig = new HttpConfiguration(config);
149e17bfc96SThomas Wolf 			secureConnector = new ServerConnector(server,
150e17bfc96SThomas Wolf 					new SslConnectionFactory(sslContextFactory,
151e17bfc96SThomas Wolf 							HttpVersion.HTTP_1_1.asString()),
152e17bfc96SThomas Wolf 					new HttpConnectionFactory(secureConfig));
153e17bfc96SThomas Wolf 			secureConnector.setPort(sslPort);
154e17bfc96SThomas Wolf 			secureConnector.setHost(ip);
155e17bfc96SThomas Wolf 		} else {
156e17bfc96SThomas Wolf 			secureConfig = null;
157e17bfc96SThomas Wolf 			secureConnector = null;
158e17bfc96SThomas Wolf 		}
159e17bfc96SThomas Wolf 
16051e2646dSJens Baumgart 		contexts = new ContextHandlerCollection();
16151e2646dSJens Baumgart 
16251e2646dSJens Baumgart 		log = new TestRequestLog();
16351e2646dSJens Baumgart 		log.setHandler(contexts);
16451e2646dSJens Baumgart 
165e17bfc96SThomas Wolf 		if (secureConnector == null) {
16651e2646dSJens Baumgart 			server.setConnectors(new Connector[] { connector });
167e17bfc96SThomas Wolf 		} else {
168e17bfc96SThomas Wolf 			server.setConnectors(
169e17bfc96SThomas Wolf 					new Connector[] { connector, secureConnector });
170e17bfc96SThomas Wolf 		}
17151e2646dSJens Baumgart 		server.setHandler(log);
17251e2646dSJens Baumgart 	}
17351e2646dSJens Baumgart 
createTestSslContextFactory(String hostName)174e17bfc96SThomas Wolf 	private SslContextFactory createTestSslContextFactory(String hostName) {
1758e356fc4SDavid Pursehouse 		SslContextFactory.Client factory = new SslContextFactory.Client(true);
176e17bfc96SThomas Wolf 
177e17bfc96SThomas Wolf 		String dName = "CN=,OU=,O=,ST=,L=,C=";
178e17bfc96SThomas Wolf 
179e17bfc96SThomas Wolf 		try {
180e17bfc96SThomas Wolf 			File tmpDir = Files.createTempDirectory("jks").toFile();
181e17bfc96SThomas Wolf 			tmpDir.deleteOnExit();
182e17bfc96SThomas Wolf 			makePrivate(tmpDir);
183e17bfc96SThomas Wolf 			File keyStore = new File(tmpDir, "keystore.jks");
184e17bfc96SThomas Wolf 			Runtime.getRuntime().exec(
185e17bfc96SThomas Wolf 					new String[] {
186e17bfc96SThomas Wolf 							"keytool", //
187e17bfc96SThomas Wolf 							"-keystore", keyStore.getAbsolutePath(), //
188e17bfc96SThomas Wolf 							"-storepass", keyPassword,
189e17bfc96SThomas Wolf 							"-alias", hostName, //
190e17bfc96SThomas Wolf 							"-genkeypair", //
191e17bfc96SThomas Wolf 							"-keyalg", "RSA", //
192e17bfc96SThomas Wolf 							"-keypass", keyPassword, //
193e17bfc96SThomas Wolf 							"-dname", dName, //
194e17bfc96SThomas Wolf 							"-validity", "2" //
195e17bfc96SThomas Wolf 					}).waitFor();
196e17bfc96SThomas Wolf 			keyStore.deleteOnExit();
197e17bfc96SThomas Wolf 			makePrivate(keyStore);
198e17bfc96SThomas Wolf 			filesToDelete.add(keyStore);
199e17bfc96SThomas Wolf 			filesToDelete.add(tmpDir);
200e17bfc96SThomas Wolf 			factory.setKeyStorePath(keyStore.getAbsolutePath());
201e17bfc96SThomas Wolf 			factory.setKeyStorePassword(keyPassword);
202e17bfc96SThomas Wolf 			factory.setKeyManagerPassword(keyPassword);
203e17bfc96SThomas Wolf 			factory.setTrustStorePath(keyStore.getAbsolutePath());
204e17bfc96SThomas Wolf 			factory.setTrustStorePassword(keyPassword);
205e17bfc96SThomas Wolf 		} catch (InterruptedException | IOException e) {
206e17bfc96SThomas Wolf 			throw new RuntimeException("Cannot create ssl key/certificate", e);
207e17bfc96SThomas Wolf 		}
208e17bfc96SThomas Wolf 		return factory;
209e17bfc96SThomas Wolf 	}
210e17bfc96SThomas Wolf 
makePrivate(File file)211e17bfc96SThomas Wolf 	private void makePrivate(File file) {
212e17bfc96SThomas Wolf 		file.setReadable(false);
213e17bfc96SThomas Wolf 		file.setWritable(false);
214e17bfc96SThomas Wolf 		file.setExecutable(false);
215e17bfc96SThomas Wolf 		file.setReadable(true, true);
216e17bfc96SThomas Wolf 		file.setWritable(true, true);
217e17bfc96SThomas Wolf 		if (file.isDirectory()) {
218e17bfc96SThomas Wolf 			file.setExecutable(true, true);
219e17bfc96SThomas Wolf 		}
220e17bfc96SThomas Wolf 	}
221e17bfc96SThomas Wolf 
22251e2646dSJens Baumgart 	/**
22351e2646dSJens Baumgart 	 * Create a new servlet context within the server.
22451e2646dSJens Baumgart 	 * <p>
22551e2646dSJens Baumgart 	 * This method should be invoked before the server is started, once for each
22651e2646dSJens Baumgart 	 * context the caller wants to register.
22751e2646dSJens Baumgart 	 *
22851e2646dSJens Baumgart 	 * @param path
22951e2646dSJens Baumgart 	 *            path of the context; use "/" for the root context if binding
23051e2646dSJens Baumgart 	 *            to the root is desired.
23151e2646dSJens Baumgart 	 * @return the context to add servlets into.
23251e2646dSJens Baumgart 	 */
addContext(String path)23351e2646dSJens Baumgart 	public ServletContextHandler addContext(String path) {
23451e2646dSJens Baumgart 		assertNotYetSetUp();
23551e2646dSJens Baumgart 		if ("".equals(path))
23651e2646dSJens Baumgart 			path = "/";
23751e2646dSJens Baumgart 
23851e2646dSJens Baumgart 		ServletContextHandler ctx = new ServletContextHandler();
23951e2646dSJens Baumgart 		ctx.setContextPath(path);
24051e2646dSJens Baumgart 		contexts.addHandler(ctx);
24151e2646dSJens Baumgart 
24251e2646dSJens Baumgart 		return ctx;
24351e2646dSJens Baumgart 	}
24451e2646dSJens Baumgart 
2450b131b73SMatthias Sohn 	/**
2460b131b73SMatthias Sohn 	 * Configure basic authentication.
2470b131b73SMatthias Sohn 	 *
2480b131b73SMatthias Sohn 	 * @param ctx
2490b131b73SMatthias Sohn 	 * @param methods
2500b131b73SMatthias Sohn 	 * @return servlet context handler
2510b131b73SMatthias Sohn 	 */
authBasic(ServletContextHandler ctx, String... methods)2527ac1bfc8SThomas Wolf 	public ServletContextHandler authBasic(ServletContextHandler ctx,
2537ac1bfc8SThomas Wolf 			String... methods) {
25451e2646dSJens Baumgart 		assertNotYetSetUp();
2557ac1bfc8SThomas Wolf 		auth(ctx, new BasicAuthenticator(), methods);
25651e2646dSJens Baumgart 		return ctx;
25751e2646dSJens Baumgart 	}
25851e2646dSJens Baumgart 
25933bbbc3cSMat Booth 	static class TestMappedLoginService extends AbstractLoginService {
2609a4e8de4SMatthias Sohn 		private String role;
26151e2646dSJens Baumgart 
262*0f442d70SThomas Wolf 		protected final Map<String, UserPrincipal> users = new ConcurrentHashMap<>();
26333bbbc3cSMat Booth 
TestMappedLoginService(String role)264da6fa14fSDavid Pursehouse 		TestMappedLoginService(String role) {
2659a4e8de4SMatthias Sohn 			this.role = role;
2669a4e8de4SMatthias Sohn 		}
26751e2646dSJens Baumgart 
26851e2646dSJens Baumgart 		@Override
doStart()26933bbbc3cSMat Booth 		protected void doStart() throws Exception {
27033bbbc3cSMat Booth 			UserPrincipal p = new UserPrincipal(username,
27133bbbc3cSMat Booth 					new Password(password));
27233bbbc3cSMat Booth 			users.put(username, p);
27333bbbc3cSMat Booth 			super.doStart();
27451e2646dSJens Baumgart 		}
27551e2646dSJens Baumgart 
27651e2646dSJens Baumgart 		@Override
loadRoleInfo(UserPrincipal user)27733bbbc3cSMat Booth 		protected String[] loadRoleInfo(UserPrincipal user) {
27898cdca9bSDavid Pursehouse 			if (users.get(user.getName()) == null) {
27933bbbc3cSMat Booth 				return null;
28098cdca9bSDavid Pursehouse 			}
28133bbbc3cSMat Booth 			return new String[] { role };
28251e2646dSJens Baumgart 		}
2839aa3748eSMatthias Sohn 
284fc24c5e1SMatthias Sohn 		@Override
loadUserInfo(String user)28533bbbc3cSMat Booth 		protected UserPrincipal loadUserInfo(String user) {
28633bbbc3cSMat Booth 			return users.get(user);
28751e2646dSJens Baumgart 		}
2889a4e8de4SMatthias Sohn 	}
28951e2646dSJens Baumgart 
createConstraintMapping()2907ac1bfc8SThomas Wolf 	private ConstraintMapping createConstraintMapping() {
29151e2646dSJens Baumgart 		ConstraintMapping cm = new ConstraintMapping();
29251e2646dSJens Baumgart 		cm.setConstraint(new Constraint());
29351e2646dSJens Baumgart 		cm.getConstraint().setAuthenticate(true);
29451e2646dSJens Baumgart 		cm.getConstraint().setDataConstraint(Constraint.DC_NONE);
2957ac1bfc8SThomas Wolf 		cm.getConstraint().setRoles(new String[] { authRole });
29651e2646dSJens Baumgart 		cm.setPathSpec("/*");
2977ac1bfc8SThomas Wolf 		return cm;
2987ac1bfc8SThomas Wolf 	}
2997ac1bfc8SThomas Wolf 
auth(ServletContextHandler ctx, Authenticator authType, String... methods)3007ac1bfc8SThomas Wolf 	private void auth(ServletContextHandler ctx, Authenticator authType,
3017ac1bfc8SThomas Wolf 			String... methods) {
3027ac1bfc8SThomas Wolf 		AbstractLoginService users = new TestMappedLoginService(authRole);
3037ac1bfc8SThomas Wolf 		List<ConstraintMapping> mappings = new ArrayList<>();
3047ac1bfc8SThomas Wolf 		if (methods == null || methods.length == 0) {
3057ac1bfc8SThomas Wolf 			mappings.add(createConstraintMapping());
3067ac1bfc8SThomas Wolf 		} else {
3077ac1bfc8SThomas Wolf 			for (String method : methods) {
3087ac1bfc8SThomas Wolf 				ConstraintMapping cm = createConstraintMapping();
3097ac1bfc8SThomas Wolf 				cm.setMethod(method.toUpperCase(Locale.ROOT));
3107ac1bfc8SThomas Wolf 				mappings.add(cm);
3117ac1bfc8SThomas Wolf 			}
3127ac1bfc8SThomas Wolf 		}
31351e2646dSJens Baumgart 
31451e2646dSJens Baumgart 		ConstraintSecurityHandler sec = new ConstraintSecurityHandler();
31551e2646dSJens Baumgart 		sec.setRealmName(realm);
31651e2646dSJens Baumgart 		sec.setAuthenticator(authType);
31751e2646dSJens Baumgart 		sec.setLoginService(users);
3187ac1bfc8SThomas Wolf 		sec.setConstraintMappings(
3192fc00af4SMichael Keppler 				mappings.toArray(new ConstraintMapping[0]));
32051e2646dSJens Baumgart 		sec.setHandler(ctx);
32151e2646dSJens Baumgart 
32251e2646dSJens Baumgart 		contexts.removeHandler(ctx);
32351e2646dSJens Baumgart 		contexts.addHandler(sec);
32451e2646dSJens Baumgart 	}
32551e2646dSJens Baumgart 
32651e2646dSJens Baumgart 	/**
32751e2646dSJens Baumgart 	 * Start the server on a random local port.
32851e2646dSJens Baumgart 	 *
32951e2646dSJens Baumgart 	 * @throws Exception
33051e2646dSJens Baumgart 	 *             the server cannot be started, testing is not possible.
33151e2646dSJens Baumgart 	 */
setUp()33251e2646dSJens Baumgart 	public void setUp() throws Exception {
33351e2646dSJens Baumgart 		RecordingLogger.clear();
33451e2646dSJens Baumgart 		log.clear();
33551e2646dSJens Baumgart 		server.start();
336e17bfc96SThomas Wolf 		config.setSecurePort(getSecurePort());
337e17bfc96SThomas Wolf 		if (secureConfig != null) {
338e17bfc96SThomas Wolf 			secureConfig.setSecurePort(getSecurePort());
339e17bfc96SThomas Wolf 		}
34051e2646dSJens Baumgart 	}
34151e2646dSJens Baumgart 
34251e2646dSJens Baumgart 	/**
34351e2646dSJens Baumgart 	 * Shutdown the server.
34451e2646dSJens Baumgart 	 *
34551e2646dSJens Baumgart 	 * @throws Exception
34651e2646dSJens Baumgart 	 *             the server refuses to halt, or wasn't running.
34751e2646dSJens Baumgart 	 */
tearDown()34851e2646dSJens Baumgart 	public void tearDown() throws Exception {
34951e2646dSJens Baumgart 		RecordingLogger.clear();
35051e2646dSJens Baumgart 		log.clear();
35151e2646dSJens Baumgart 		server.stop();
352e17bfc96SThomas Wolf 		for (File f : filesToDelete) {
353e17bfc96SThomas Wolf 			f.delete();
354e17bfc96SThomas Wolf 		}
355e17bfc96SThomas Wolf 		filesToDelete.clear();
35651e2646dSJens Baumgart 	}
35751e2646dSJens Baumgart 
35851e2646dSJens Baumgart 	/**
35951e2646dSJens Baumgart 	 * Get the URI to reference this server.
36051e2646dSJens Baumgart 	 * <p>
36151e2646dSJens Baumgart 	 * The returned URI includes the proper host name and port number, but does
36251e2646dSJens Baumgart 	 * not contain a path.
36351e2646dSJens Baumgart 	 *
36451e2646dSJens Baumgart 	 * @return URI to reference this server's root context.
36551e2646dSJens Baumgart 	 */
getURI()36651e2646dSJens Baumgart 	public URI getURI() {
36751e2646dSJens Baumgart 		assertAlreadySetUp();
36851e2646dSJens Baumgart 		String host = connector.getHost();
36951e2646dSJens Baumgart 		if (host.contains(":") && !host.startsWith("["))
37051e2646dSJens Baumgart 			host = "[" + host + "]";
37151e2646dSJens Baumgart 		final String uri = "http://" + host + ":" + getPort();
37251e2646dSJens Baumgart 		try {
37351e2646dSJens Baumgart 			return new URI(uri);
37451e2646dSJens Baumgart 		} catch (URISyntaxException e) {
37551e2646dSJens Baumgart 			throw new RuntimeException("Unexpected URI error on " + uri, e);
37651e2646dSJens Baumgart 		}
37751e2646dSJens Baumgart 	}
37851e2646dSJens Baumgart 
3790b131b73SMatthias Sohn 	/**
3800b131b73SMatthias Sohn 	 * Get port.
3810b131b73SMatthias Sohn 	 *
3820b131b73SMatthias Sohn 	 * @return the local port number the server is listening on.
3830b131b73SMatthias Sohn 	 */
getPort()38451e2646dSJens Baumgart 	public int getPort() {
38551e2646dSJens Baumgart 		assertAlreadySetUp();
386a24b7c3cSMatthias Sohn 		return connector.getLocalPort();
38751e2646dSJens Baumgart 	}
38851e2646dSJens Baumgart 
3890b131b73SMatthias Sohn 	/**
3900b131b73SMatthias Sohn 	 * Get secure port.
3910b131b73SMatthias Sohn 	 *
3920b131b73SMatthias Sohn 	 * @return the HTTPS port or -1 if not configured.
3930b131b73SMatthias Sohn 	 */
getSecurePort()394e17bfc96SThomas Wolf 	public int getSecurePort() {
395e17bfc96SThomas Wolf 		assertAlreadySetUp();
396e17bfc96SThomas Wolf 		return secureConnector != null ? secureConnector.getLocalPort() : -1;
397e17bfc96SThomas Wolf 	}
398e17bfc96SThomas Wolf 
3990b131b73SMatthias Sohn 	/**
4000b131b73SMatthias Sohn 	 * Get requests.
4010b131b73SMatthias Sohn 	 *
4020b131b73SMatthias Sohn 	 * @return all requests since the server was started.
4030b131b73SMatthias Sohn 	 */
getRequests()40451e2646dSJens Baumgart 	public List<AccessEvent> getRequests() {
4053b444863SDavid Pursehouse 		return new ArrayList<>(log.getEvents());
40651e2646dSJens Baumgart 	}
40751e2646dSJens Baumgart 
40851e2646dSJens Baumgart 	/**
4090b131b73SMatthias Sohn 	 * Get requests.
4100b131b73SMatthias Sohn 	 *
41151e2646dSJens Baumgart 	 * @param base
41251e2646dSJens Baumgart 	 *            base URI used to access the server.
41351e2646dSJens Baumgart 	 * @param path
41451e2646dSJens Baumgart 	 *            the path to locate requests for, relative to {@code base}.
41551e2646dSJens Baumgart 	 * @return all requests which match the given path.
41651e2646dSJens Baumgart 	 */
getRequests(URIish base, String path)41751e2646dSJens Baumgart 	public List<AccessEvent> getRequests(URIish base, String path) {
41851e2646dSJens Baumgart 		return getRequests(HttpTestCase.join(base, path));
41951e2646dSJens Baumgart 	}
42051e2646dSJens Baumgart 
42151e2646dSJens Baumgart 	/**
4220b131b73SMatthias Sohn 	 * Get requests.
4230b131b73SMatthias Sohn 	 *
42451e2646dSJens Baumgart 	 * @param path
42551e2646dSJens Baumgart 	 *            the path to locate requests for.
42651e2646dSJens Baumgart 	 * @return all requests which match the given path.
42751e2646dSJens Baumgart 	 */
getRequests(String path)42851e2646dSJens Baumgart 	public List<AccessEvent> getRequests(String path) {
4293b444863SDavid Pursehouse 		ArrayList<AccessEvent> r = new ArrayList<>();
43051e2646dSJens Baumgart 		for (AccessEvent event : log.getEvents()) {
43151e2646dSJens Baumgart 			if (event.getPath().equals(path)) {
43251e2646dSJens Baumgart 				r.add(event);
43351e2646dSJens Baumgart 			}
43451e2646dSJens Baumgart 		}
43551e2646dSJens Baumgart 		return r;
43651e2646dSJens Baumgart 	}
43751e2646dSJens Baumgart 
assertNotYetSetUp()43851e2646dSJens Baumgart 	private void assertNotYetSetUp() {
439d9e07a57SRobin Rosenberg 		assertFalse("server is not running", server.isRunning());
44051e2646dSJens Baumgart 	}
44151e2646dSJens Baumgart 
assertAlreadySetUp()44251e2646dSJens Baumgart 	private void assertAlreadySetUp() {
443d9e07a57SRobin Rosenberg 		assertTrue("server is running", server.isRunning());
44451e2646dSJens Baumgart 	}
44551e2646dSJens Baumgart }
446