package perf.test; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils; import org.codehaus.jackson.JsonFactory; import perf.test.utils.BackendResponse; import perf.test.utils.ServiceResponseBuilder; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Servlet implementation class TestServlet */ public class TestCaseAServlet extends HttpServlet { private static final long serialVersionUID = 1L; private final static JsonFactory jsonFactory = new JsonFactory(); private final HttpClient client; // used for parallel execution of requests private final ThreadPoolExecutor executor; private final String backendMockUriPrefix; public TestCaseAServlet() { String backendMockHost = System.getProperty("perf.test.backend.hostname", "127.0.0.1"); String backendMockPort = System.getProperty("perf.test.backend.port", "8989"); backendMockUriPrefix = "http://" + backendMockHost + ':' + backendMockPort + "/ws-backend-mock"; final RequestConfig reqConfig = RequestConfig.custom() .setConnectTimeout(PropertyNames.ClientConnectTimeout.getValueAsInt()) .setSocketTimeout(PropertyNames.ClientSocketTimeout.getValueAsInt()) .setConnectionRequestTimeout(PropertyNames.ClientConnectionRequestTimeout.getValueAsInt()) .build(); // don't care about total vs. per-route right now, will set them to the same final PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(); connMgr.setMaxTotal(PropertyNames.ClientMaxConnectionsTotal.getValueAsInt()); connMgr.setDefaultMaxPerRoute(PropertyNames.ClientMaxConnectionsTotal.getValueAsInt()); client = HttpClients.custom() .setDefaultRequestConfig(reqConfig) .setConnectionManager(connMgr) .build(); // used for parallel execution final int backendRequestThreadPoolSize = PropertyNames.BackendRequestThreadPoolSize.getValueAsInt(); // setting core and max pool sizes the same since I do not want any queueing in here executor = new ThreadPoolExecutor(backendRequestThreadPoolSize, backendRequestThreadPoolSize, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>()); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); try { Object _id = request.getParameter("id"); if (_id == null) { response.getWriter().println("Please provide a numerical 'id' value. It can be a random number (uuid)."); response.setStatus(500); return; } final long id = Long.parseLong(String.valueOf(_id)); try { /* First 2 requests (A, B) in parallel */ final Future<BackendResponse> aResponseFuture = queueGet("/mock.json?type=A&numItems=2&itemSize=50&delay=50&id=" + id); final Future<BackendResponse> bResponseFuture = queueGet("/mock.json?type=B&numItems=25&itemSize=30&delay=150&id=" + id); /* When response A received perform C & D */ // spawned in another thread so we don't block the ability to B/E to proceed in parallel Future<BackendResponse[]> aGroupResponses = executor.submit(new Callable<BackendResponse[]>() { @Override public BackendResponse[] call() throws Exception { BackendResponse aResponse = aResponseFuture.get(); final Future<BackendResponse> cResponse = queueGet( "/mock.json?type=C&numItems=1&itemSize=5000&delay=80&id=" + aResponse.getResponseKey()); final Future<BackendResponse> dResponse = queueGet( "/mock.json?type=D&numItems=1&itemSize=1000&delay=1&id=" + aResponse.getResponseKey()); return new BackendResponse[] { aResponse, cResponse.get(), dResponse.get() }; } }); /* When response B is received perform E */ BackendResponse b = bResponseFuture.get(); BackendResponse e = get("/mock.json?type=E&numItems=100&itemSize=30&delay=4&id=" + b.getResponseKey()); /* * Parse JSON so we can extract data and combine data into a single response. * * This simulates what real web-services do most of the time. */ BackendResponse a = aGroupResponses.get()[0]; BackendResponse c = aGroupResponses.get()[1]; BackendResponse d = aGroupResponses.get()[2]; //EventLogger.log(requestId, "build-response-start"); ByteArrayOutputStream bos = ServiceResponseBuilder.buildTestAResponse(jsonFactory, a, b, c, d, e); //EventLogger.log(requestId, "build-response-end"); // output to stream //EventLogger.log(requestId, "flush-response-start"); response.getWriter().write(bos.toString()); //EventLogger.log(requestId, "flush-response-end"); } catch (Exception e) { // error that needs to be returned response.setStatus(500); response.getWriter().println("Error: " + e.getMessage()); e.printStackTrace(); } } finally { ServiceResponseBuilder.addResponseHeaders(response, startTime); } } public Future<BackendResponse> queueGet(final String url) { final Future<BackendResponse> f = executor.submit(new Callable<BackendResponse>() { @Override public BackendResponse call() throws Exception { return get(url); } }); return f; } public BackendResponse get(String url) { String uri = backendMockUriPrefix + url; HttpGet httpGet = new HttpGet(uri); try { HttpResponse response = client.execute(httpGet); if (response.getStatusLine().getStatusCode() != 200) { throw new RuntimeException("Failure: " + response.getStatusLine()); } HttpEntity entity = response.getEntity(); BackendResponse backendResponse = BackendResponse.fromJson(jsonFactory, entity.getContent()); // ensure it is fully consumed EntityUtils.consume(entity); return backendResponse; } catch (Exception e) { throw new RuntimeException("Failure retrieving: " + uri, e); } finally { httpGet.releaseConnection(); } } }