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);
+               }
+       }
+}

Reply via email to