这是indexloc提供的服务,不要输入任何密码
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core-java-modules/core-java-httpclient/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ This module contains articles about Java HttpClient
- [Posting with Java HttpClient](https://www.baeldung.com/java-httpclient-post)
- [Custom HTTP Header With the Java HttpClient](https://www.baeldung.com/java-http-client-custom-header)
- [Java HttpClient Connection Management](https://www.baeldung.com/java-httpclient-connection-management)
- [Java HttpClient Exception-handling Demo](https://github.com/haqer1/java-http-client-demo)
30 changes: 28 additions & 2 deletions core-java-modules/core-java-httpclient/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,46 @@
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>${junit.suite.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<logback.configurationFile>${logback.configurationFileName}</logback.configurationFile>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>

<properties>
<mockserver.version>5.11.2</mockserver.version>
<wiremock.version>3.9.1</wiremock.version>
<wiremock.version>3.12.1</wiremock.version>
<junit.suite.version>1.11.4</junit.suite.version>
<logback.configurationFileName>logback.xml</logback.configurationFileName>
</properties>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.httpclient.conn;
package com.baeldung.httpclient;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
Expand Down Expand Up @@ -27,11 +27,9 @@ public class HttpClientConnectionManagementUnitTest {
.dynamicPort();
WireMockServer firstServer = new WireMockServer(firstConfiguration);
WireMockServer secondServer = new WireMockServer(secondConfiguration);
private String firstUrl;
private String secondUrl;

private HttpClient client = HttpClient.newHttpClient();
private HttpClient secondClient = HttpClient.newHttpClient();
private final HttpClient client = HttpClient.newHttpClient();
private final HttpClient secondClient = HttpClient.newHttpClient();

private HttpRequest getRequest;
private HttpRequest secondGet;
Expand All @@ -53,8 +51,8 @@ public void setup() {
.aResponse()
.withStatus(200)));

firstUrl = "http://localhost:" + firstServer.port() + "/first";
secondUrl = "http://localhost:" + secondServer.port() + "/second";
String firstUrl = "http://localhost:" + firstServer.port() + "/first";
String secondUrl = "http://localhost:" + secondServer.port() + "/second";

getRequest = HttpRequest
.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package com.baeldung.httpclient;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.http.Fault;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

/**
* Demonstrates establishment of new connections by HttpClient on exceptions during interactions with server.
*
* @author Resat SABIQ
* @see <a href="https://github.com/haqer1/java-http-client-demo">java-http-client-demo</a>
*/
public class JavaHttpClientExceptionHandlingUnitTest {
private final static org.slf4j.Logger logger
= org.slf4j.LoggerFactory.getLogger(JavaHttpClientExceptionHandlingUnitTest.class.getName());
private final static String CONNECTION_RESET_BY_PEER_CONFIG_MSG = "WireMock pre-configured for RESET.";
private final static String CONNECTION_RESET_BY_PEER_DONE_MSG = "Making assertions & exiting from RESET test.";
private final static String LOG_FILEPATH = "logback-HttpConnection.log";
private final static String NEW_HTTP_CONN_MSG_PATTERN
= "o.e.jetty.server.HttpConnection - New HTTP Connection HttpConnection@";

private final static byte READ_TIMEOUT_SECONDS = 30;
private final static int READ_TIMEOUT_DELAY_MS = 60000;
private final static String CONNECTION_READ_TIMEOUT_CONFIG_MSG = MessageFormat.format(
"WireMock pre-configured for TIMEOUT ({0} seconds).", READ_TIMEOUT_SECONDS);
private final static String CONNECTION_READ_TIMEOUT_DONE_MSG = "Making assertions & exiting from TIMEOUT test.";
private final static long WIRE_MOCK_CONCURRENT_LOGGING_AVOIDANCE_DELAY_STEP = 1;
private final static long WIRE_MOCK_CONCURRENT_LOGGING_AVOIDANCE_SIZE_THRESHOLD = 600;

private final WireMockConfiguration mockConfiguration = WireMockConfiguration.options().dynamicPort();
private final WireMockServer mockServer = new WireMockServer(mockConfiguration);
private HttpRequest getRequest;
private final HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
private byte minimumConnectionsExpected;

/**
* Lets {@link HttpClientConnectionManagementUnitTest} log first, to avoid message overwriting issues.
* @throws InterruptedException if sleeping is interrupted externally
* @throws IOException in case of external IO issue
*/
@BeforeAll
public static void ensureReliableWireMockLogging() throws IOException, InterruptedException {
Path logPath = Path.of(LOG_FILEPATH);
long size;
do {
size = Files.size(logPath);
logger.debug("Sleeping 1s 4 reliable logging (file size: {})", size);
TimeUnit.SECONDS.sleep(WIRE_MOCK_CONCURRENT_LOGGING_AVOIDANCE_DELAY_STEP);
} while (size < WIRE_MOCK_CONCURRENT_LOGGING_AVOIDANCE_SIZE_THRESHOLD);
}

@BeforeEach
public void setup() {
mockServer.start();
String firstUrl = "http://localhost:" + mockServer.port() + "/first";

getRequest = HttpRequest
.newBuilder()
.uri(URI.create(firstUrl))
.timeout(Duration.ofSeconds(READ_TIMEOUT_SECONDS))
.version(HttpClient.Version.HTTP_1_1)
.build();
}

private void stubFor200() {
mockServer.stubFor(WireMock
.get(WireMock.anyUrl())
.willReturn(WireMock
.aResponse()
.withStatus(200)));
}

@AfterEach
public void tearDown() {
List<ServeEvent> firstWiremockAllServeEvents = mockServer.getAllServeEvents();
List<String> urlList = firstWiremockAllServeEvents
.stream()
.map(event -> event
.getRequest()
.getAbsoluteUrl()).collect(Collectors.toUnmodifiableList());

logger.debug("All URLs before supplementary assertion: {}", urlList);

assertTrue(urlList.size() >= minimumConnectionsExpected
, "Throws tests must result in connections established 2 times");

mockServer.stop();
}

/**
* Upon IOException on a request HttpClient makes a new connection to the server immediately to
* replace the current one in the internal pool.
*/
@Test
public final void givenHttpClient_whenIOExceptionThrown_thenThePoolConnectionIsReplacedWithNewOneImmediately()
throws IOException {
minimumConnectionsExpected = 2;

mockServer.stubFor(WireMock
.get(WireMock.anyUrl())
.willReturn(
WireMock.aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)
)
);
logger.debug(CONNECTION_RESET_BY_PEER_CONFIG_MSG);

assertThrows(IOException.class, () -> client.send(getRequest, HttpResponse.BodyHandlers.ofString()));

logger.debug(CONNECTION_RESET_BY_PEER_DONE_MSG);

assertCountOfNewConnections(CONNECTION_RESET_BY_PEER_CONFIG_MSG, minimumConnectionsExpected
, CONNECTION_RESET_BY_PEER_DONE_MSG);
}

private void assertCountOfNewConnections(String startMsg, byte countExpected, String endMessage) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(LOG_FILEPATH));
String line;
boolean resetPreconfigured = false, exiting = false;
byte newConnections = 0;
while (!exiting && (line = reader.readLine()) != null) {
if (line.contains(startMsg))
resetPreconfigured = true;
else if (line.contains(NEW_HTTP_CONN_MSG_PATTERN) && resetPreconfigured)
newConnections++;
else if (line.contains(endMessage))
exiting = true;
}

assertTrue (resetPreconfigured, "Must contain: " +startMsg);
assertEquals(countExpected, newConnections
, "Must contain twice: " + NEW_HTTP_CONN_MSG_PATTERN);
assertTrue (exiting, "Must contain: " +endMessage);
}

/**
* Upon HttpTimeoutException during reading a request HttpClient makes a new connection to the server upon new
* request to replace the current one in the internal pool.
*/
@Test
public final void
givenHttpClient_whenTimeoutExceptionThrown_thenThePoolConnectionIsReplacedWithNewOneOnNewRequest()
throws IOException, InterruptedException {
minimumConnectionsExpected = 2;

mockServer.stubFor(WireMock
.get(WireMock.anyUrl())
.willReturn(WireMock.aResponse().withStatus(200).withFixedDelay(READ_TIMEOUT_DELAY_MS))
);
logger.debug(CONNECTION_READ_TIMEOUT_CONFIG_MSG);

assertThrows(HttpTimeoutException.class, () ->
client.send(getRequest, HttpResponse.BodyHandlers.ofString())
);

stubFor200();
HttpResponse<String> response = client.send(getRequest, HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode(), "Status 200 expected without timeout");

logger.debug(CONNECTION_READ_TIMEOUT_DONE_MSG);

assertCountOfNewConnections(CONNECTION_READ_TIMEOUT_CONFIG_MSG, minimumConnectionsExpected
, CONNECTION_READ_TIMEOUT_DONE_MSG);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,20 @@

<logger name="org.eclipse.jetty.server.handler" level="WARN"/>
<logger name="org.mockserver.log" level="WARN"/>
</configuration>

<appender name="FILE-HttpConnection" class="ch.qos.logback.core.FileAppender">
<file>logback-HttpConnection.log</file>
<append>false</append>
<immediateFlush>true</immediateFlush>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.eclipse.jetty.server" level="WARN"/>
<logger name="com.baeldung.httpclient.JavaHttpClientExceptionHandlingUnitTest" level="DEBUG">
<appender-ref ref="FILE-HttpConnection" />
</logger>
<logger name="org.eclipse.jetty.server.HttpConnection" level="DEBUG">
<appender-ref ref="FILE-HttpConnection" />
</logger>
</configuration>