View Javadoc
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&lt;User&gt; future = asyncClient.executeAsync(request);
35   *
36   * // Handle result when ready
37   * future.thenAccept(user -&gt; System.out.println("Got: " + user.getName()))
38   *       .exceptionally(ex -&gt; {
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 -&gt; System.out.println(user));
53   * </code></pre>
54   *
55   * @author guinetik &lt;guinetik@gmail.com&gt;
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 }