package perf.test.rxnetty; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.logging.LogLevel; import io.reactivex.netty.RxNetty; import io.reactivex.netty.protocol.http.client.HttpClient; import io.reactivex.netty.protocol.http.client.HttpClientBuilder; import io.reactivex.netty.protocol.http.client.HttpClientRequest; import io.reactivex.netty.protocol.http.client.HttpClientResponse; import io.reactivex.netty.protocol.http.server.HttpServerRequest; import io.reactivex.netty.protocol.http.server.HttpServerResponse; import java.io.ByteArrayOutputStream; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.codehaus.jackson.JsonFactory; import perf.test.utils.BackendResponse; import perf.test.utils.JsonParseException; import perf.test.utils.ServiceResponseBuilder; import rx.Observable; /** * Handle request and return a response that composes multiple backend services like this: * * A) GET http://hostname:9100/mock.json?numItems=2&itemSize=50&delay=50&id={uuid} * B) GET http://hostname:9100/mock.json?numItems=25&itemSize=30&delay=150&id={uuid} * C) GET http://hostname:9100/mock.json?numItems=1&itemSize=5000&delay=80&id={a.responseKey} * D) GET http://hostname:9100/mock.json?numItems=1&itemSize=1000&delay=1&id={a.responseKey} * E) GET http://hostname:9100/mock.json?numItems=100&itemSize=30&delay=4&id={b.responseKey} */ public class TestRouteBasic { private static JsonFactory jsonFactory = new JsonFactory(); private final String host; private final int port; private final HttpClient<ByteBuf, ByteBuf> client; private ConnectionPoolMetricListener stats; public TestRouteBasic(String backendHost, int backendPort) { host = backendHost; port = backendPort; client = new HttpClientBuilder<ByteBuf, ByteBuf>(host, port) .withMaxConnections(10000) .config(new HttpClient.HttpClientConfig.Builder().readTimeout(1, TimeUnit.MINUTES).build()) .build(); stats = new ConnectionPoolMetricListener(); client.subscribe(stats); } public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { long startTime = System.currentTimeMillis(); List<String> _id = request.getQueryParameters().get("id"); if (_id == null || _id.size() != 1) { return writeError(request, response, "Please provide a numerical 'id' value. It can be a random number (uuid)."); } long id = Long.parseLong(String.valueOf(_id.get(0))); Observable<List<BackendResponse>> acd = getDataFromBackend("/mock.json?numItems=2&itemSize=50&delay=50&id=" + id) .doOnError(Throwable::printStackTrace) // Eclipse 20140224-0627 can't infer without this type hint even though the Java 8 compiler can .<List<BackendResponse>> flatMap(responseA -> { Observable<BackendResponse> responseC = getDataFromBackend( "/mock.json?numItems=1&itemSize=5000&delay=80&id=" + responseA.getResponseKey()); Observable<BackendResponse> responseD = getDataFromBackend( "/mock.json?numItems=1&itemSize=1000&delay=1&id=" + responseA.getResponseKey()); return Observable.zip(Observable.just(responseA), responseC, responseD, Arrays::asList); }).doOnError(Throwable::printStackTrace); Observable<List<BackendResponse>> be = getDataFromBackend("/mock.json?numItems=25&itemSize=30&delay=150&id=" + id) // Eclipse 20140224-0627 can't infer without this type hint even though the Java 8 compiler can .<List<BackendResponse>> flatMap(responseB -> { Observable<BackendResponse> responseE = getDataFromBackend( "/mock.json?numItems=100&itemSize=30&delay=4&id=" + responseB.getResponseKey()); return Observable.zip(Observable.just(responseB), responseE, Arrays::asList); }).doOnError(Throwable::printStackTrace); return Observable.zip(acd, be, (_acd, _be) -> { BackendResponse responseA = _acd.get(0); BackendResponse responseB = _be.get(0); BackendResponse responseC = _acd.get(1); BackendResponse responseD = _acd.get(2); BackendResponse responseE = _be.get(1); return new BackendResponse[] { responseA, responseB, responseC, responseD, responseE }; }).flatMap(backendResponses -> { try { ByteArrayOutputStream responseStream = ServiceResponseBuilder.buildTestAResponse(jsonFactory, backendResponses); // set response header response.getHeaders().addHeader("Content-Type", "application/json"); // performance headers addResponseHeaders(response, startTime); int contentLength = responseStream.size(); response.getHeaders().addHeader("Content-Length", contentLength); return response.writeBytesAndFlush(responseStream.toByteArray()); } catch (Exception e) { return writeError(request, response, "Failed: " + e.getMessage()); } }).doOnError(Throwable::printStackTrace); } public HttpClient<ByteBuf, ByteBuf> getClient() { return client; } /** * Add various headers used for logging and statistics. */ private static void addResponseHeaders(HttpServerResponse<?> response, long startTime) { Map<String, String> perfResponseHeaders = ServiceResponseBuilder.getPerfResponseHeaders(startTime); for (Map.Entry<String, String> entry : perfResponseHeaders.entrySet()) { response.getHeaders().add(entry.getKey(), entry.getValue()); } } private Observable<BackendResponse> getDataFromBackend(String url) { return client.submit(HttpClientRequest.createGet(url)).flatMap((HttpClientResponse<ByteBuf> r) -> { Observable<BackendResponse> bytesToJson = r.getContent().flatMap(b -> { try { return Observable.just(BackendResponse.fromJson(jsonFactory, new ByteBufInputStream(b))); } catch (JsonParseException e) { return Observable.error(e); } }); return bytesToJson; }); } private static Observable<Void> writeError(HttpServerRequest<?> request, HttpServerResponse<?> response, String message) { System.err.println("Server => Error [" + request.getPath() + "] => " + message); response.setStatus(HttpResponseStatus.BAD_REQUEST); return response.writeStringAndFlush("Error 500: " + message + "\n"); } public ConnectionPoolMetricListener getStats() { return stats; } }