diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/PipelinedHttpResponseCallback.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/PipelinedHttpResponseCallback.java new file mode 100644 index 000000000..a5a8d643a --- /dev/null +++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/PipelinedHttpResponseCallback.java @@ -0,0 +1,56 @@ +package io.refactoring.http5.client.example.async.helper; + +import io.refactoring.http5.client.example.RequestProcessingException; +import java.util.concurrent.CountDownLatch; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.message.StatusLine; + +/** The pipelined http response callback. */ +@Slf4j +public class PipelinedHttpResponseCallback implements FutureCallback { + /** The Http get request. */ + private final SimpleHttpRequest httpRequest; + + /** The Error message. */ + private final String errorMessage; + + /** The Latch. */ + private final CountDownLatch latch; + + /** + * Instantiates a new pipelined http response callback. + * + * @param httpRequest the http request + * @param errorMessage the error message + * @param latch the latch + */ + public PipelinedHttpResponseCallback( + SimpleHttpRequest httpRequest, String errorMessage, CountDownLatch latch) { + this.httpRequest = httpRequest; + this.errorMessage = errorMessage; + this.latch = latch; + } + + @Override + public void completed(final SimpleHttpResponse response) { + latch.countDown(); + log.debug(httpRequest + "->" + new StatusLine(response)); + log.debug("Got response: {}", response.getBody()); + } + + @Override + public void failed(final Exception ex) { + latch.countDown(); + log.error(httpRequest + "->" + ex); + throw new RequestProcessingException(errorMessage, ex); + } + + @Override + public void cancelled() { + latch.countDown(); + log.debug(httpRequest + " cancelled"); + } +} diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleCharResponseConsumer.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleCharResponseConsumer.java new file mode 100644 index 000000000..0ee6b634e --- /dev/null +++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleCharResponseConsumer.java @@ -0,0 +1,73 @@ +package io.refactoring.http5.client.example.async.helper; + +import io.refactoring.http5.client.example.RequestProcessingException; +import java.io.IOException; +import java.nio.CharBuffer; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.StatusLine; + +/** The Simple http character stream consumer. */ +@Slf4j +public class SimpleCharResponseConsumer extends AbstractCharResponseConsumer { + /** The Http get request. */ + private final SimpleHttpRequest httpRequest; + + private final StringBuilder responseBuilder = new StringBuilder(); + + /** The Error message. */ + private final String errorMessage; + + /** + * Instantiates a new Simple http response callback. + * + * @param httpRequest the http request + * @param errorMessage the error message + */ + public SimpleCharResponseConsumer(SimpleHttpRequest httpRequest, String errorMessage) { + this.httpRequest = httpRequest; + this.errorMessage = errorMessage; + } + + @Override + protected void start(HttpResponse httpResponse, ContentType contentType) + throws HttpException, IOException { + log.debug(httpRequest + "->" + new StatusLine(httpResponse)); + responseBuilder.setLength(0); + } + + @Override + protected SimpleHttpResponse buildResult() throws IOException { + return SimpleHttpResponse.create(HttpStatus.SC_OK, responseBuilder.toString()); + } + + @Override + protected int capacityIncrement() { + return 0; + } + + @Override + protected void data(CharBuffer src, boolean endOfStream) throws IOException { + while (src.hasRemaining()) { + responseBuilder.append(src.get()); + } + if (endOfStream) { + log.debug(responseBuilder.toString()); + } + } + + @Override + public void failed(Exception ex) { + log.error(httpRequest + "->" + ex); + throw new RequestProcessingException(errorMessage, ex); + } + + @Override + public void releaseResources() {} +} diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleHttpResponseCallback.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleHttpResponseCallback.java new file mode 100644 index 000000000..5db683d79 --- /dev/null +++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/SimpleHttpResponseCallback.java @@ -0,0 +1,49 @@ +package io.refactoring.http5.client.example.async.helper; + +import io.refactoring.http5.client.example.RequestProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.message.StatusLine; + +import java.util.HashMap; +import java.util.Map; + +/** The Simple http response callback. */ +@Slf4j +public class SimpleHttpResponseCallback implements FutureCallback { + /** The Http get request. */ + private final SimpleHttpRequest httpRequest; + + /** The Error message. */ + private final String errorMessage; + + /** + * Instantiates a new Simple http response callback. + * + * @param httpRequest the http request + * @param errorMessage the error message + */ + public SimpleHttpResponseCallback(SimpleHttpRequest httpRequest, String errorMessage) { + this.httpRequest = httpRequest; + this.errorMessage = errorMessage; + } + + @Override + public void completed(final SimpleHttpResponse response) { + log.debug(httpRequest + "->" + new StatusLine(response)); + log.debug("Got response: {}", response.getBody()); + } + + @Override + public void failed(final Exception ex) { + log.error(httpRequest + "->" + ex); + throw new RequestProcessingException(errorMessage, ex); + } + + @Override + public void cancelled() { + log.debug(httpRequest + " cancelled"); + } +} diff --git a/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelper.java b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelper.java new file mode 100644 index 000000000..1808531bb --- /dev/null +++ b/apache-http-client/src/main/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelper.java @@ -0,0 +1,390 @@ +package io.refactoring.http5.client.example.async.helper; + +import io.refactoring.http5.client.example.RequestProcessingException; +import io.refactoring.http5.client.example.helper.BaseHttpRequestHelper; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.async.methods.*; +import org.apache.hc.client5.http.config.TlsConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.config.Http1Config; +import org.apache.hc.core5.http.nio.AsyncClientEndpoint; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http.nio.support.BasicRequestProducer; +import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.net.URIBuilder; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.util.Timeout; + +/** Handles HTTP requests for user entities. It uses built in types for HTTP processing. */ +@Slf4j +public class UserAsyncHttpRequestHelper extends BaseHttpRequestHelper { + + private CloseableHttpAsyncClient httpClient; + + private MinimalHttpAsyncClient minimalHttp1Client; + private MinimalHttpAsyncClient minimalHttp2Client; + + /** Starts http async client. */ + public void startHttpAsyncClient() { + if (httpClient == null) { + try { + final PoolingAsyncClientConnectionManager cm = + PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(getTlsStrategy()) + .build(); + final IOReactorConfig ioReactorConfig = + IOReactorConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build(); + httpClient = + HttpAsyncClients.custom() + .setIOReactorConfig(ioReactorConfig) + .setConnectionManager(cm) + .build(); + httpClient.start(); + log.debug("Started HTTP async client."); + } catch (Exception e) { + final String errorMsg = "Failed to start HTTP async client."; + log.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + } + + private TlsStrategy getTlsStrategy() + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + // Trust standard CA and those trusted by our custom strategy + final SSLContext sslContext = + SSLContexts.custom() + // Custom TrustStrategy implementations are intended for verification + // of certificates whose CA is not trusted by the system, and where specifying + // a custom truststore containing the certificate chain is not an option. + .loadTrustMaterial( + (chain, authType) -> { + // Please note that validation of the server certificate without validation + // of the entire certificate chain in this example is preferred to completely + // disabling trust verification, however, this still potentially allows + // for man-in-the-middle attacks. + final X509Certificate cert = chain[0]; + log.warn( + "Bypassing SSL certificate validation for {}", + cert.getSubjectX500Principal().getName()); + return true; + }) + .build(); + + final TlsStrategy tlsStrategy = + ClientTlsStrategyBuilder.create().setSslContext(sslContext).build(); + return tlsStrategy; + } + + /** + * Starts http 1 async client. + * + * @return the minimal http async client + */ + public MinimalHttpAsyncClient startMinimalHttp1AsyncClient() { + if (minimalHttp1Client == null) { + minimalHttp1Client = startMinimalHttpAsyncClient(HttpVersionPolicy.FORCE_HTTP_1); + } + return minimalHttp1Client; + } + + /** + * Starts http 2 async client. + * + * @return minimal http async client + */ + public MinimalHttpAsyncClient startMinimalHttp2AsyncClient() { + if (minimalHttp2Client == null) { + minimalHttp2Client = startMinimalHttpAsyncClient(HttpVersionPolicy.FORCE_HTTP_2); + } + return minimalHttp2Client; + } + + /** + * Starts http async client. + * + * @return minimal Http client; + */ + private MinimalHttpAsyncClient startMinimalHttpAsyncClient(HttpVersionPolicy httpVersionPolicy) { + try { + final MinimalHttpAsyncClient minimalHttpClient = + HttpAsyncClients.createMinimal( + H2Config.DEFAULT, + Http1Config.DEFAULT, + IOReactorConfig.DEFAULT, + PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(getTlsStrategy()) + .setDefaultTlsConfig( + TlsConfig.custom().setVersionPolicy(httpVersionPolicy).build()) + .build()); + minimalHttpClient.start(); + log.debug("Started minimal HTTP async client for {}.", httpVersionPolicy); + return minimalHttpClient; + } catch (Exception e) { + final String errorMsg = "Failed to start minimal HTTP async client."; + log.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** Stops http async client. */ + public void stopHttpAsyncClient() { + if (httpClient != null) { + log.info("Shutting down http async client."); + httpClient.close(CloseMode.GRACEFUL); + httpClient = null; + } + } + + /** + * Stops minimal http async client. + * + * @param minimalHttpClient the minimal http client + */ + public void stopMinimalHttpAsyncClient(MinimalHttpAsyncClient minimalHttpClient) { + if (minimalHttpClient != null) { + log.info("Shutting down minimal http async client."); + minimalHttpClient.close(CloseMode.GRACEFUL); + minimalHttpClient = null; + } + } + + /** + * Gets all users for given ids using async callback. + * + * @param userIdList user id list + * @param delayInSec the delay in seconds by which server will send the response + * @return response if user is found + * @throws RequestProcessingException if failed to execute request + */ + public Map getUserWithCallback(final List userIdList, final int delayInSec) + throws RequestProcessingException { + Objects.requireNonNull(httpClient, "Make sure that HTTP Async client is started."); + final Map userResponseMap = new HashMap<>(); + final Map> futuresMap = new HashMap<>(); + for (Long userId : userIdList) { + try { + // Create request + final HttpHost httpHost = HttpHost.create("https://reqres.in"); + final URI uri = new URIBuilder("/api/users/" + userId + "?delay=" + delayInSec).build(); + final SimpleHttpRequest httpGetRequest = + SimpleRequestBuilder.get().setHttpHost(httpHost).setPath(uri.getPath()).build(); + log.debug( + "Executing {} request: {} on host {}", + httpGetRequest.getMethod(), + httpGetRequest.getUri(), + httpHost); + + final Future future = + httpClient.execute( + SimpleRequestProducer.create(httpGetRequest), + SimpleResponseConsumer.create(), + new SimpleHttpResponseCallback( + httpGetRequest, + MessageFormat.format("Failed to get user for ID: {0}", userId))); + futuresMap.put(userId, future); + } catch (Exception e) { + final String message = MessageFormat.format("Failed to get user for ID: {0}", userId); + log.error(message, e); + userResponseMap.put(userId, message); + } + } + + log.debug("Got {} futures.", futuresMap.size()); + for (Map.Entry> futureEntry : futuresMap.entrySet()) { + final Long userId = futureEntry.getKey(); + try { + userResponseMap.put(userId, futureEntry.getValue().get().getBodyText()); + } catch (Exception e) { + final String message = MessageFormat.format("Failed to get user for ID: {0}", userId); + log.error(message, e); + userResponseMap.put(userId, message); + } + } + + return userResponseMap; + } + + /** + * Gets all users for given ids using streams. + * + * @param userIdList user id list + * @param delayInSec the delay in seconds by which server will send the response + * @return response if user is found + * @throws RequestProcessingException if failed to execute request + */ + public Map getUserWithStreams(final List userIdList, final int delayInSec) + throws RequestProcessingException { + Objects.requireNonNull(httpClient, "Make sure that HTTP Async client is started."); + final Map userResponseMap = new HashMap<>(); + final Map> futuresMap = new HashMap<>(); + for (Long userId : userIdList) { + try { + // Create request + final HttpHost httpHost = HttpHost.create("https://reqres.in"); + final URI uri = new URIBuilder("/api/users/" + userId + "?delay=" + delayInSec).build(); + final SimpleHttpRequest httpGetRequest = + SimpleRequestBuilder.get().setHttpHost(httpHost).setPath(uri.getPath()).build(); + log.debug( + "Executing {} request: {} on host {}", + httpGetRequest.getMethod(), + httpGetRequest.getUri(), + httpHost); + + final Future future = + httpClient.execute( + new BasicRequestProducer(httpGetRequest, null), + new SimpleCharResponseConsumer( + httpGetRequest, MessageFormat.format("Failed to get user for ID: {0}", userId)), + null); + futuresMap.put(userId, future); + } catch (Exception e) { + final String message = MessageFormat.format("Failed to get user for ID: {0}", userId); + log.error(message, e); + userResponseMap.put(userId, message); + } + } + + log.debug("Got {} futures.", futuresMap.size()); + for (Map.Entry> futureEntry : futuresMap.entrySet()) { + final Long userId = futureEntry.getKey(); + try { + userResponseMap.put(userId, futureEntry.getValue().get().getBodyText()); + } catch (Exception e) { + final String message = MessageFormat.format("Failed to get user for ID: {0}", userId); + log.error(message, e); + userResponseMap.put(userId, message); + } + } + + return userResponseMap; + } + + /** + * Gets all users for given ids using pipelining. + * + * @param minimalHttpClient the minimal http client + * @param userIdList user id list + * @param delayInSec the delay in seconds by which server will send the response + * @return response if user is found + * @throws RequestProcessingException if failed to execute request + */ + public Map getUserWithPipelining( + final MinimalHttpAsyncClient minimalHttpClient, + final List userIdList, + final int delayInSec) + throws RequestProcessingException { + return getUserWithParallelRequests(minimalHttpClient, userIdList, delayInSec); + } + + /** + * Gets all users for given ids using multiplexing. + * + * @param minimalHttpClient the minimal http client + * @param userIdList user id list + * @param delayInSec the delay in seconds by which server will send the response + * @return response if user is found + * @throws RequestProcessingException if failed to execute request + */ + public Map getUserWithMultiplexing( + final MinimalHttpAsyncClient minimalHttpClient, + final List userIdList, + final int delayInSec) + throws RequestProcessingException { + return getUserWithParallelRequests(minimalHttpClient, userIdList, delayInSec); + } + + private Map getUserWithParallelRequests( + final MinimalHttpAsyncClient minimalHttpClient, + final List userIdList, + final int delayInSec) + throws RequestProcessingException { + + Objects.requireNonNull( + minimalHttpClient, "Make sure that minimal HTTP Async client is started."); + final Map userResponseMap = new HashMap<>(); + final Map> futuresMap = new HashMap<>(); + AsyncClientEndpoint endpoint = null; + Long userId = null; + + try { + final HttpHost httpHost = HttpHost.create("https://reqres.in"); + final Future leaseFuture = minimalHttpClient.lease(httpHost, null); + endpoint = leaseFuture.get(30, TimeUnit.SECONDS); + final CountDownLatch latch = new CountDownLatch(userIdList.size()); + + for (Long currentUserId : userIdList) { + userId = currentUserId; + // Create request + final URI uri = new URIBuilder("/api/users/" + userId + "?delay=" + delayInSec).build(); + final SimpleHttpRequest httpGetRequest = + SimpleRequestBuilder.get().setHttpHost(httpHost).setPath(uri.getPath()).build(); + log.debug( + "Executing {} request: {} on host {}", + httpGetRequest.getMethod(), + httpGetRequest.getUri(), + httpHost); + + final Future future = + minimalHttpClient.execute( + SimpleRequestProducer.create(httpGetRequest), + SimpleResponseConsumer.create(), + new PipelinedHttpResponseCallback( + httpGetRequest, + MessageFormat.format("Failed to get user for ID: {0}", userId), + latch)); + futuresMap.put(userId, future); + } + + latch.await(); + } catch (Exception e) { + if (userId != null) { + final String message = MessageFormat.format("Failed to get user for ID: {0}", userId); + log.error(message, e); + userResponseMap.put(userId, message); + } else { + throw new RequestProcessingException("Failed to process request.", e); + } + } finally { + if (endpoint != null) { + endpoint.releaseAndReuse(); + } + } + + log.debug("Got {} futures.", futuresMap.size()); + + for (Map.Entry> futureEntry : futuresMap.entrySet()) { + final Long currentUserId = futureEntry.getKey(); + try { + userResponseMap.put(currentUserId, futureEntry.getValue().get().getBodyText()); + } catch (Exception e) { + final String message = + MessageFormat.format("Failed to get user for ID: {0}", currentUserId); + log.error(message, e); + userResponseMap.put(currentUserId, message); + } + } + + return userResponseMap; + } +} diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/BaseAsyncExampleTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/BaseAsyncExampleTests.java new file mode 100644 index 000000000..22346d212 --- /dev/null +++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/BaseAsyncExampleTests.java @@ -0,0 +1,8 @@ +package io.refactoring.http5.client.example.async.helper; + +import io.refactoring.http5.client.example.BaseExampleTests; +import io.refactoring.http5.client.example.util.JsonUtils; + +abstract class BaseAsyncExampleTests extends BaseExampleTests { + protected final JsonUtils jsonUtils = new JsonUtils(); +} diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelperTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelperTests.java new file mode 100644 index 000000000..c13af9c09 --- /dev/null +++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/async/helper/UserAsyncHttpRequestHelperTests.java @@ -0,0 +1,133 @@ +package io.refactoring.http5.client.example.async.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** User async http request helper tests. */ +class UserAsyncHttpRequestHelperTests extends BaseAsyncExampleTests { + + private final UserAsyncHttpRequestHelper userHttpRequestHelper = new UserAsyncHttpRequestHelper(); + + private final Condition getUserErrorCheck = + new Condition("Check failure response.") { + @Override + public boolean matches(String value) { + // value should not be null + // value should not be failure message + return value != null && !value.startsWith("Failed to get user"); + } + }; + + /** Tests get user. */ + @Test + void getUserWithCallback() { + try { + userHttpRequestHelper.startHttpAsyncClient(); + + // Send 10 requests in parallel + // call the delayed endpoint + final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L); + final Map responseBodyMap = + userHttpRequestHelper.getUserWithCallback(userIdList, 3); + + // verify + assertThat(responseBodyMap) + .hasSameSizeAs(userIdList) + .doesNotContainKey(null) + .doesNotContainValue(null) + .hasValueSatisfying(getUserErrorCheck); + + } catch (Exception e) { + Assertions.fail("Failed to execute HTTP request.", e); + } finally { + userHttpRequestHelper.stopHttpAsyncClient(); + } + } + + /** Tests get user with stream. */ + @Test + void getUserWithStream() { + try { + userHttpRequestHelper.startHttpAsyncClient(); + + // Send 10 requests in parallel + // call the delayed endpoint + final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L); + final Map responseBodyMap = + userHttpRequestHelper.getUserWithStreams(userIdList, 3); + + // verify + assertThat(responseBodyMap) + .hasSameSizeAs(userIdList) + .doesNotContainKey(null) + .doesNotContainValue(null) + .hasValueSatisfying(getUserErrorCheck); + + } catch (Exception e) { + Assertions.fail("Failed to execute HTTP request.", e); + } finally { + userHttpRequestHelper.stopHttpAsyncClient(); + } + } + + /** Tests get user with pipelining. */ + @Test + void getUserWithPipelining() { + MinimalHttpAsyncClient minimalHttpAsyncClient = null; + try { + minimalHttpAsyncClient = userHttpRequestHelper.startMinimalHttp1AsyncClient(); + + // Send 10 requests in parallel + // call the delayed endpoint + final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L); + final Map responseBodyMap = + userHttpRequestHelper.getUserWithPipelining(minimalHttpAsyncClient, userIdList, 3); + + // verify + assertThat(responseBodyMap) + .hasSameSizeAs(userIdList) + .doesNotContainKey(null) + .doesNotContainValue(null) + .hasValueSatisfying(getUserErrorCheck); + + } catch (Exception e) { + Assertions.fail("Failed to execute HTTP request.", e); + } finally { + userHttpRequestHelper.stopMinimalHttpAsyncClient(minimalHttpAsyncClient); + } + } + + /** Tests get user with multiplexing. */ + @Test + void getUserWithMultiplexing() { + MinimalHttpAsyncClient minimalHttpAsyncClient = null; + try { + minimalHttpAsyncClient = userHttpRequestHelper.startMinimalHttp2AsyncClient(); + + // Send 10 requests in parallel + // call the delayed endpoint + final List userIdList = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L); + final Map responseBodyMap = + userHttpRequestHelper.getUserWithMultiplexing(minimalHttpAsyncClient, userIdList, 3); + + // verify + assertThat(responseBodyMap) + .hasSameSizeAs(userIdList) + .doesNotContainKey(null) + .doesNotContainValue(null) + .hasValueSatisfying(getUserErrorCheck); + + } catch (Exception e) { + Assertions.fail("Failed to execute HTTP request.", e); + } finally { + userHttpRequestHelper.stopMinimalHttpAsyncClient(minimalHttpAsyncClient); + } + } +} diff --git a/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java b/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java index cce7fa9fd..a8c38bb0d 100644 --- a/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java +++ b/apache-http-client/src/test/java/io/refactoring/http5/client/example/util/HttpConfigurationHelperTests.java @@ -1,6 +1,6 @@ package io.refactoring.http5.client.example.util; -import io.refactoring.http5.client.example.classic.helper.HttpConfigurationHelper; +import io.refactoring.http5.client.example.config.helper.HttpConfigurationHelper; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.junit.jupiter.api.Test;