This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 83728c6c70 Next Generation RestClient
83728c6c70 is described below
commit 83728c6c709a32b442e6caa96242cb82b99b823b
Author: James Bognar <[email protected]>
AuthorDate: Thu Apr 9 14:03:06 2026 -0400
Next Generation RestClient
---
.../pom.xml | 88 +++++++
.../apachehttpclient45/ApacheHc45Transport.java | 182 ++++++++++++++
.../ApacheHc45TransportBuilder.java | 70 ++++++
.../ApacheHc45TransportProvider.java | 61 +++++
...che.juneau.ng.rest.client.HttpTransportProvider | 16 ++
juneau-rest/pom.xml | 1 +
juneau-utest/pom.xml | 12 +
.../juneau/ng/rest/ApacheHc45Transport_Test.java | 274 +++++++++++++++++++++
8 files changed, 704 insertions(+)
diff --git a/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/pom.xml
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/pom.xml
new file mode 100644
index 0000000000..b47560d9e4
--- /dev/null
+++ b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest</artifactId>
+ <version>9.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>juneau-ng-rest-client-apache-httpclient-45</artifactId>
+ <name>Apache Juneau NG REST Client — Apache HttpClient 4.5
Transport</name>
+ <description>Apache HttpClient 4.5 transport adapter for the
next-generation Juneau REST client.</description>
+ <packaging>bundle</packaging>
+
+ <properties>
+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.5.14</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+
<supportIncrementalBuild>true</supportIncrementalBuild>
+ </configuration>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git
a/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45Transport.java
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45Transport.java
new file mode 100644
index 0000000000..d5cd4bfdaa
--- /dev/null
+++
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45Transport.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.ng.rest.client.apachehttpclient45;
+
+import java.io.*;
+
+import org.apache.http.*;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.*;
+import org.apache.http.impl.client.*;
+import org.apache.juneau.ng.rest.client.*;
+
+/**
+ * {@link HttpTransport} implementation backed by Apache HttpClient 4.5.
+ *
+ * <p>
+ * This transport is auto-discovered via {@link java.util.ServiceLoader} when
+ * {@code org.apache.httpcomponents:httpclient} is on the classpath. You can
also instantiate it explicitly:
+ *
+ * <p class='bjava'>
+ * <jv>transport</jv> = ApacheHc45Transport.<jsm>builder</jsm>()
+ * .httpClient(HttpClients.createDefault())
+ * .build();
+ *
+ * <jv>client</jv> = NgRestClient.<jsm>builder</jsm>()
+ * .transport(<jv>transport</jv>)
+ * .build();
+ * </p>
+ *
+ * <p>
+ * <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
+ * ({@code org.apache.juneau.ng.*}).
+ * It is not API-frozen: binary- and source-incompatible changes may appear in
the <b>next major</b> Juneau release
+ * (and possibly earlier).
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/juneau-ng-rest-client">juneau-ng
REST client</a>
+ * </ul>
+ *
+ * @since 9.2.1
+ */
+@SuppressWarnings({
+ "resource" // httpClient is owned by this transport and closed in
close()
+})
+public final class ApacheHc45Transport implements HttpTransport {
+
+ private final CloseableHttpClient httpClient;
+
+ ApacheHc45Transport(ApacheHc45TransportBuilder builder) {
+ this.httpClient = builder.httpClient != null ?
builder.httpClient : HttpClients.createDefault();
+ }
+
+ /**
+ * Returns a new builder for this transport.
+ *
+ * @return A new builder. Never <jk>null</jk>.
+ */
+ public static ApacheHc45TransportBuilder builder() {
+ return new ApacheHc45TransportBuilder();
+ }
+
+ /**
+ * Returns a new instance backed by a default {@link
CloseableHttpClient}.
+ *
+ * @return A new instance. Never <jk>null</jk>.
+ */
+ public static ApacheHc45Transport create() {
+ return builder().build();
+ }
+
+ @Override /* HttpTransport */
+ public TransportResponse execute(TransportRequest request) throws
TransportException {
+ var hcRequest = buildHcRequest(request);
+ CloseableHttpResponse hcResponse;
+ try {
+ hcResponse = httpClient.execute(hcRequest);
+ } catch (IOException e) {
+ throw new TransportException("HTTP transport error: " +
e.getMessage(), e);
+ }
+ return buildTransportResponse(hcResponse);
+ }
+
+ @Override /* Closeable */
+ public void close() throws IOException {
+ httpClient.close();
+ }
+
+ //
-----------------------------------------------------------------------------------------------------------------
+ // Internal helpers
+ //
-----------------------------------------------------------------------------------------------------------------
+
+ private static HttpUriRequest buildHcRequest(TransportRequest request)
throws TransportException {
+ var builder =
RequestBuilder.create(request.getMethod()).setUri(request.getUri());
+ for (var h : request.getHeaders())
+ builder.addHeader(h.name(), h.value());
+ var body = request.getBody();
+ if (body != null)
+ builder.setEntity(new TransportBodyEntity(body));
+ try {
+ return builder.build();
+ } catch (Exception e) {
+ throw new TransportException("Failed to build HTTP
request: " + e.getMessage(), e);
+ }
+ }
+
+ private static TransportResponse
buildTransportResponse(CloseableHttpResponse hcResponse) throws
TransportException {
+ var statusLine = hcResponse.getStatusLine();
+ var builder = TransportResponse.builder()
+ .statusCode(statusLine.getStatusCode())
+ .reasonPhrase(statusLine.getReasonPhrase())
+ .closeCallback(hcResponse);
+ for (var h : hcResponse.getAllHeaders())
+ builder.header(h.getName(), h.getValue());
+ var entity = hcResponse.getEntity();
+ if (entity != null) {
+ try {
+ builder.body(entity.getContent());
+ } catch (IOException e) {
+ throw new TransportException("Failed to read
response body: " + e.getMessage(), e);
+ }
+ }
+ return builder.build();
+ }
+
+ //
-----------------------------------------------------------------------------------------------------------------
+ // TransportBodyEntity — bridges TransportBody to Apache HttpEntity
+ //
-----------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Bridges a {@link TransportBody} to Apache HttpClient's {@link
AbstractHttpEntity}.
+ */
+ private static final class TransportBodyEntity extends
AbstractHttpEntity {
+
+ private final TransportBody body;
+
+ TransportBodyEntity(TransportBody body) {
+ this.body = body;
+ var ct = body.getContentType();
+ if (ct != null)
+ setContentType(ct);
+ }
+
+ @Override /* HttpEntity */
+ public boolean isRepeatable() {
+ return body.isRepeatable();
+ }
+
+ @Override /* HttpEntity */
+ public long getContentLength() {
+ return body.getContentLength();
+ }
+
+ @Override /* HttpEntity */
+ public InputStream getContent() throws
UnsupportedOperationException {
+ throw new UnsupportedOperationException("Use
writeTo(OutputStream) instead");
+ }
+
+ @Override /* HttpEntity */
+ public void writeTo(OutputStream out) throws IOException {
+ body.writeTo(out);
+ }
+
+ @Override /* HttpEntity */
+ public boolean isStreaming() {
+ return !body.isRepeatable();
+ }
+ }
+}
diff --git
a/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45TransportBuilder.java
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45TransportBuilder.java
new file mode 100644
index 0000000000..f5306751c1
--- /dev/null
+++
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45TransportBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.ng.rest.client.apachehttpclient45;
+
+import org.apache.http.impl.client.*;
+
+/**
+ * Fluent builder for {@link ApacheHc45Transport}.
+ *
+ * <p>
+ * Obtain an instance via {@link ApacheHc45Transport#builder()}.
+ *
+ * <p>
+ * <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
+ * ({@code org.apache.juneau.ng.*}).
+ * It is not API-frozen: binary- and source-incompatible changes may appear in
the <b>next major</b> Juneau release
+ * (and possibly earlier).
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/juneau-ng-rest-client">juneau-ng
REST client</a>
+ * </ul>
+ *
+ * @since 9.2.1
+ */
+public final class ApacheHc45TransportBuilder {
+
+ CloseableHttpClient httpClient;
+
+ ApacheHc45TransportBuilder() {}
+
+ /**
+ * Sets the underlying {@link CloseableHttpClient} to use.
+ *
+ * <p>
+ * If not set, a default client is created via {@link
HttpClients#createDefault()}.
+ *
+ * <p>
+ * The transport takes ownership of the client and will close it when
{@link ApacheHc45Transport#close()} is called.
+ *
+ * @param value The client to use. Must not be <jk>null</jk>.
+ * @return This object.
+ */
+ public ApacheHc45TransportBuilder httpClient(CloseableHttpClient value)
{
+ httpClient = value;
+ return this;
+ }
+
+ /**
+ * Builds and returns the {@link ApacheHc45Transport}.
+ *
+ * @return A new instance. Never <jk>null</jk>.
+ */
+ public ApacheHc45Transport build() {
+ return new ApacheHc45Transport(this);
+ }
+}
diff --git
a/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45TransportProvider.java
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45TransportProvider.java
new file mode 100644
index 0000000000..32d7a06057
--- /dev/null
+++
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/java/org/apache/juneau/ng/rest/client/apachehttpclient45/ApacheHc45TransportProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.ng.rest.client.apachehttpclient45;
+
+import org.apache.juneau.ng.rest.client.*;
+
+/**
+ * {@link HttpTransportProvider} implementation that supplies an {@link
ApacheHc45Transport}.
+ *
+ * <p>
+ * Registered via {@code
META-INF/services/org.apache.juneau.ng.rest.client.HttpTransportProvider} so
that
+ * {@code NgRestClient} can auto-discover Apache HttpClient 4.5 when this
module is on the classpath.
+ *
+ * <p>
+ * <b>Beta — API subject to change:</b> This type is part of the
next-generation REST client and HTTP stack
+ * ({@code org.apache.juneau.ng.*}).
+ * It is not API-frozen: binary- and source-incompatible changes may appear in
the <b>next major</b> Juneau release
+ * (and possibly earlier).
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/juneau-ng-rest-client">juneau-ng
REST client</a>
+ * </ul>
+ *
+ * @since 9.2.1
+ */
+public final class ApacheHc45TransportProvider implements
HttpTransportProvider {
+
+ @Override /* HttpTransportProvider */
+ public int getPriority() {
+ return 50;
+ }
+
+ @Override /* HttpTransportProvider */
+ public boolean isAvailable() {
+ try {
+
Class.forName("org.apache.http.impl.client.CloseableHttpClient");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Override /* HttpTransportProvider */
+ public HttpTransport create() {
+ return ApacheHc45Transport.create();
+ }
+}
diff --git
a/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/resources/META-INF/services/org.apache.juneau.ng.rest.client.HttpTransportProvider
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/resources/META-INF/services/org.apache.juneau.ng.rest.client.HttpTransportProvider
new file mode 100644
index 0000000000..730cc08b49
--- /dev/null
+++
b/juneau-rest/juneau-ng-rest-client-apache-httpclient-45/src/main/resources/META-INF/services/org.apache.juneau.ng.rest.client.HttpTransportProvider
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+org.apache.juneau.ng.rest.client.apachehttpclient45.ApacheHc45TransportProvider
diff --git a/juneau-rest/pom.xml b/juneau-rest/pom.xml
index 36002414b4..cde05c0514 100644
--- a/juneau-rest/pom.xml
+++ b/juneau-rest/pom.xml
@@ -37,6 +37,7 @@
<module>juneau-rest-server-springboot</module>
<module>juneau-rest-client</module>
<module>juneau-rest-mock</module>
+ <module>juneau-ng-rest-client-apache-httpclient-45</module>
</modules>
<build>
diff --git a/juneau-utest/pom.xml b/juneau-utest/pom.xml
index 40d444800e..4d8794c9d0 100644
--- a/juneau-utest/pom.xml
+++ b/juneau-utest/pom.xml
@@ -85,6 +85,18 @@
<artifactId>juneau-rest-mock</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+
<artifactId>juneau-ng-rest-client-apache-httpclient-45</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Force commons-codec >= 1.13 to satisfy Jena's MurmurHash3
requirement;
+ httpclient:4.5.14 pulls in 1.11 which pre-dates
MurmurHash3. -->
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.19.0</version>
+ </dependency>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server</artifactId>
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/ng/rest/ApacheHc45Transport_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/ng/rest/ApacheHc45Transport_Test.java
new file mode 100644
index 0000000000..38db2c80ca
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/ng/rest/ApacheHc45Transport_Test.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.ng.rest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+import java.net.*;
+import java.nio.charset.*;
+
+import org.apache.juneau.ng.http.entity.*;
+import org.apache.juneau.ng.rest.client.*;
+import org.apache.juneau.ng.rest.client.apachehttpclient45.*;
+import org.junit.jupiter.api.*;
+
+import com.sun.net.httpserver.*;
+
+/**
+ * Integration tests for {@link ApacheHc45Transport} against a real embedded
HTTP server.
+ */
+@SuppressWarnings("restriction")
+public class ApacheHc45Transport_Test {
+
+ private static HttpServer server;
+ private static int port;
+
+ @BeforeAll
+ static void startServer() throws IOException {
+ server = HttpServer.create(new InetSocketAddress(0), 0);
+ port = server.getAddress().getPort();
+
+ server.createContext("/hello", exchange -> {
+ var body = "Hello,
World!".getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type",
"text/plain");
+ exchange.sendResponseHeaders(200, body.length);
+ exchange.getResponseBody().write(body);
+ exchange.close();
+ });
+
+ server.createContext("/echo-method", exchange -> {
+ var body =
exchange.getRequestMethod().getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type",
"text/plain");
+ exchange.sendResponseHeaders(200, body.length);
+ exchange.getResponseBody().write(body);
+ exchange.close();
+ });
+
+ server.createContext("/echo-body", exchange -> {
+ var requestBody =
exchange.getRequestBody().readAllBytes();
+ exchange.getResponseHeaders().add("Content-Type",
"text/plain");
+ exchange.sendResponseHeaders(200, requestBody.length);
+ exchange.getResponseBody().write(requestBody);
+ exchange.close();
+ });
+
+ server.createContext("/echo-header", exchange -> {
+ var headerValue =
exchange.getRequestHeaders().getFirst("X-Custom");
+ var body = (headerValue != null ? headerValue :
"missing").getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type",
"text/plain");
+ exchange.sendResponseHeaders(200, body.length);
+ exchange.getResponseBody().write(body);
+ exchange.close();
+ });
+
+ server.createContext("/not-found", exchange -> {
+ exchange.sendResponseHeaders(404, -1);
+ exchange.close();
+ });
+
+ server.start();
+ }
+
+ @AfterAll
+ static void stopServer() {
+ if (server != null)
+ server.stop(0);
+ }
+
+ private String rootUrl() {
+ return "http://localhost:" + port;
+ }
+
+ //
=================================================================================================================
+ // A — Basic connectivity
+ //
=================================================================================================================
+
+ @Test
+ void a01_get_basicResponse() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.get("/hello").run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("Hello, World!",
response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test
+ void a02_get_statusCode404() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.get("/not-found").run()) {
+ assertEquals(404, response.getStatusCode());
+ }
+ }
+ }
+
+ @Test
+ void a03_get_responseHeader() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.get("/hello").run()) {
+ var ct =
response.getFirstHeader("Content-Type");
+ assertNotNull(ct);
+ assertTrue(ct.value().startsWith("text/plain"),
"Expected text/plain but got: " + ct.value());
+ }
+ }
+ }
+
+ //
=================================================================================================================
+ // B — HTTP methods
+ //
=================================================================================================================
+
+ @Test
+ void b01_post_echosMethod() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.post("/echo-method")
+ .body(StringBody.of("", "text/plain"))
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("POST",
response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test
+ void b02_put_echosMethod() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.put("/echo-method")
+ .body(StringBody.of("", "text/plain"))
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("PUT", response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test
+ void b03_delete_echosMethod() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response =
client.delete("/echo-method").run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("DELETE",
response.getBodyAsString());
+ }
+ }
+ }
+
+ //
=================================================================================================================
+ // C — Request body
+ //
=================================================================================================================
+
+ @Test
+ void c01_post_stringBody() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.post("/echo-body")
+ .body(StringBody.of("hello body",
"text/plain"))
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("hello body",
response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test
+ void c02_post_byteArrayBody() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ var bytes = "byte
content".getBytes(StandardCharsets.UTF_8);
+ try (var response = client.post("/echo-body")
+ .body(ByteArrayBody.of(bytes,
"application/octet-stream"))
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("byte content",
response.getBodyAsString());
+ }
+ }
+ }
+
+ //
=================================================================================================================
+ // D — Request headers
+ //
=================================================================================================================
+
+ @Test
+ void d01_header_sentToServer() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.get("/echo-header")
+ .header("X-Custom", "my-value")
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("my-value",
response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test
+ void d02_missingHeader_returnsDefault() throws Exception {
+ var transport = ApacheHc45Transport.create();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.get("/echo-header").run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("missing",
response.getBodyAsString());
+ }
+ }
+ }
+
+ //
=================================================================================================================
+ // E — Builder: explicit httpClient
+ //
=================================================================================================================
+
+ @Test
+ void e01_builder_withExplicitHttpClient() throws Exception {
+ var transport = ApacheHc45Transport.builder()
+
.httpClient(org.apache.http.impl.client.HttpClients.createDefault())
+ .build();
+ try (var client =
NgRestClient.builder().transport(transport).rootUrl(rootUrl()).build()) {
+ try (var response = client.get("/hello").run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("Hello, World!",
response.getBodyAsString());
+ }
+ }
+ }
+
+ //
=================================================================================================================
+ // F — Provider
+ //
=================================================================================================================
+
+ @Test
+ void f01_provider_isAvailable() {
+ var provider = new
org.apache.juneau.ng.rest.client.apachehttpclient45.ApacheHc45TransportProvider();
+ assertTrue(provider.isAvailable());
+ }
+
+ @Test
+ void f02_provider_priority() {
+ var provider = new
org.apache.juneau.ng.rest.client.apachehttpclient45.ApacheHc45TransportProvider();
+ assertEquals(50, provider.getPriority());
+ }
+
+ @Test
+ void f03_provider_create() throws Exception {
+ var provider = new
org.apache.juneau.ng.rest.client.apachehttpclient45.ApacheHc45TransportProvider();
+ try (var transport = provider.create()) {
+ assertNotNull(transport);
+ }
+ }
+}