1 package com.guinetik.rr.http;
2
3 import com.guinetik.rr.RocketRestOptions;
4 import com.guinetik.rr.request.RequestSpec;
5
6 import javax.net.ssl.SSLContext;
7 import java.util.concurrent.CompletableFuture;
8 import java.util.concurrent.CompletionException;
9 import java.util.concurrent.ExecutorService;
10
11 /**
12 * Asynchronous HTTP client that executes requests on a dedicated thread pool.
13 *
14 * <p>This client wraps any synchronous {@link RocketClient} implementation and provides
15 * non-blocking request execution via {@link java.util.concurrent.CompletableFuture}.
16 *
17 * <h2>Features</h2>
18 * <ul>
19 * <li>Non-blocking request execution with CompletableFuture</li>
20 * <li>Configurable thread pool size</li>
21 * <li>Wraps any RocketClient implementation</li>
22 * <li>Proper exception propagation via CompletionException</li>
23 * </ul>
24 *
25 * <h2>Basic Usage</h2>
26 * <pre class="language-java"><code>
27 * ExecutorService executor = Executors.newFixedThreadPool(4);
28 * AsyncHttpClient asyncClient = new AsyncHttpClient(
29 * "https://api.example.com",
30 * executor
31 * );
32 *
33 * // Execute async request
34 * CompletableFuture<User> future = asyncClient.executeAsync(request);
35 *
36 * // Handle result when ready
37 * future.thenAccept(user -> System.out.println("Got: " + user.getName()))
38 * .exceptionally(ex -> {
39 * System.err.println("Failed: " + ex.getMessage());
40 * return null;
41 * });
42 *
43 * // Don't forget to shutdown
44 * asyncClient.shutdown();
45 * </code></pre>
46 *
47 * <h2>Via RocketRest</h2>
48 * <pre class="language-java"><code>
49 * RocketRest client = new RocketRest(config);
50 *
51 * client.async().get("/users/1", User.class)
52 * .thenAccept(user -> System.out.println(user));
53 * </code></pre>
54 *
55 * @author guinetik <guinetik@gmail.com>
56 * @see RocketClient
57 * @see RocketClientFactory
58 * @see com.guinetik.rr.RocketRest#async()
59 * @since 1.0.0
60 */
61 public class AsyncHttpClient implements RocketClient {
62
63 private final RocketClient delegate;
64 private final ExecutorService executor;
65
66 /**
67 * Creates a new AsyncHttpClient with the specified delegate client and executor.
68 *
69 * @param delegate The underlying HTTP client to delegate requests to
70 * @param executor The executor service to run requests on
71 */
72 public AsyncHttpClient(RocketClient delegate, ExecutorService executor) {
73 this.delegate = delegate;
74 this.executor = executor;
75 }
76
77 /**
78 * Creates a new AsyncHttpClient with a DefaultHttpClient as the delegate.
79 *
80 * @param baseUrl The base URL for API requests
81 * @param executor The executor service to run requests on
82 */
83 public AsyncHttpClient(String baseUrl, ExecutorService executor) {
84 this(new DefaultHttpClient(baseUrl), executor);
85 }
86
87 /**
88 * Creates a new AsyncHttpClient with a DefaultHttpClient as the delegate and client options.
89 *
90 * @param baseUrl The base URL for API requests
91 * @param clientOptions The client options
92 * @param executor The executor service to run requests on
93 */
94 public AsyncHttpClient(String baseUrl, RocketRestOptions clientOptions, ExecutorService executor) {
95 this(new DefaultHttpClient(baseUrl, clientOptions), executor);
96 }
97
98 @Override
99 public void configureSsl(SSLContext sslContext) {
100 delegate.configureSsl(sslContext);
101 }
102
103 @Override
104 public void setBaseUrl(String baseUrl) {
105 this.delegate.setBaseUrl(baseUrl);
106 }
107
108 @Override
109 public <Req, Res> Res execute(RequestSpec<Req, Res> requestSpec) throws RocketRestException {
110 // This method is generally not used directly with AsyncHttpClient,
111 // but it's implemented for HTTP client interface compatibility
112 return delegate.execute(requestSpec);
113 }
114
115 /**
116 * Executes an HTTP request asynchronously.
117 *
118 * @param <Req> The type of the request body
119 * @param <Res> The type of the response
120 * @param requestSpec The request specification
121 * @return A CompletableFuture that will complete with the response
122 */
123 public <Req, Res> CompletableFuture<Res> executeAsync(RequestSpec<Req, Res> requestSpec) {
124 return CompletableFuture.supplyAsync(() -> {
125 try {
126 return delegate.execute(requestSpec);
127 } catch (RocketRestException e) {
128 throw new CompletionException(e);
129 }
130 }, executor);
131 }
132
133 /**
134 * Shuts down the executor service.
135 */
136 public void shutdown() {
137 executor.shutdown();
138 }
139 }