66import java .nio .ByteBuffer ;
77import java .util .ArrayDeque ;
88import java .util .Iterator ;
9+ import java .util .Queue ;
10+ import java .util .concurrent .ArrayBlockingQueue ;
911import java .util .concurrent .CancellationException ;
1012import java .util .concurrent .ConcurrentHashMap ;
1113import java .util .concurrent .ConcurrentLinkedQueue ;
1214import java .util .concurrent .CopyOnWriteArraySet ;
15+ import java .util .concurrent .ExecutionException ;
16+ import java .util .concurrent .RejectedExecutionException ;
1317import java .util .concurrent .TimeUnit ;
1418
1519import javax .net .ssl .SSLContext ;
1822import org .threadly .concurrent .ReschedulingOperation ;
1923import org .threadly .concurrent .SingleThreadScheduler ;
2024import org .threadly .concurrent .SubmitterScheduler ;
25+ import org .threadly .concurrent .future .FutureUtils ;
2126import org .threadly .concurrent .future .ListenableFuture ;
2227import org .threadly .concurrent .future .SettableListenableFuture ;
2328import org .threadly .litesockets .Client ;
@@ -60,7 +65,7 @@ public class HTTPClient extends AbstractService {
6065 private final int maxResponseSize ;
6166 private final SubmitterScheduler ssi ;
6267 private final SocketExecuter sei ;
63- private final ConcurrentLinkedQueue <HTTPRequestWrapper > queue = new ConcurrentLinkedQueue <>() ;
68+ private final Queue <HTTPRequestWrapper > queue ;
6469 private final ConcurrentHashMap <TCPClient , HTTPRequestWrapper > inProcess = new ConcurrentHashMap <>();
6570 private final ConcurrentHashMap <HTTPAddress , ArrayDeque <Pair <Long ,TCPClient >>> sockets = new ConcurrentHashMap <>();
6671 private final CopyOnWriteArraySet <TCPClient > tcpClients = new CopyOnWriteArraySet <>();
@@ -93,12 +98,34 @@ public HTTPClient() {
9398 * @param maxResponseSize the maximum responseSize clients are allowed to send.
9499 */
95100 public HTTPClient (int maxConcurrent , int maxResponseSize ) {
101+ this (maxConcurrent , maxResponseSize , -1 );
102+ }
103+
104+ /**
105+ * <p>This constructor will let you set the max Concurrent Requests and max Response Size but will still
106+ * create its own {@link SingleThreadScheduler} to use as a threadpool.</p>
107+ *
108+ * <p>The maximum queue length parameter can help improved conditions where you want to fail fast
109+ * if the downstream service is fully consumed. Since all requests will start in the queue we
110+ * recommend to either provide a {@code 0} to leave the queue unbounded, or to set to at least
111+ * the {@code maxConcurrent} value.</p>
112+ *
113+ * @param maxConcurrent maximum number of requests to run simultaneously.
114+ * @param maxResponseSize the maximum responseSize clients are allowed to send.
115+ * @param maxQueueSize Maximum queue size, {@code <= 0} to leave unbounded. Recommended to be >= {@code maxConcurrent}
116+ */
117+ public HTTPClient (int maxConcurrent , int maxResponseSize , int maxQueueSize ) {
96118 this .maxConcurrent = maxConcurrent ;
97119 this .maxResponseSize = maxResponseSize ;
98120 sts = new SingleThreadScheduler ();
99121 this .ssi = sts ;
100122 ntse = new NoThreadSocketExecuter ();
101123 sei = ntse ;
124+ if (maxQueueSize < 1 || maxQueueSize == Integer .MAX_VALUE ) {
125+ queue = new ConcurrentLinkedQueue <>();
126+ } else {
127+ queue = new ArrayBlockingQueue <>(maxQueueSize );
128+ }
102129 runSocketTask = new RunSocket (ssi );
103130 }
104131
@@ -111,10 +138,33 @@ public HTTPClient(int maxConcurrent, int maxResponseSize) {
111138 * @param sei the SocketExecuter to use with these HTTPClients.
112139 */
113140 public HTTPClient (int maxConcurrent , int maxResponseSize , SocketExecuter sei ) {
141+ this (maxConcurrent , maxResponseSize , sei , -1 );
142+ }
143+
144+ /**
145+ * <p>This constructor will let you set the max Concurrent Requests and max Response Size
146+ * as well as your own {@link SocketExecuter} as the thread pool to use.</p>
147+ *
148+ * <p>The maximum queue length parameter can help improved conditions where you want to fail fast
149+ * if the downstream service is fully consumed. Since all requests will start in the queue we
150+ * recommend to either provide a {@code 0} to leave the queue unbounded, or to set to at least
151+ * the {@code maxConcurrent} value.</p>
152+ *
153+ * @param maxConcurrent maximum number of requests to run simultaneously.
154+ * @param maxResponseSize the maximum responseSize clients are allowed to send.
155+ * @param sei the SocketExecuter to use with these HTTPClients.
156+ * @param maxQueueSize Maximum queue size, {@code <= 0} to be unbounded. Recommended to be {@code >= maxConcurrent}
157+ */
158+ public HTTPClient (int maxConcurrent , int maxResponseSize , SocketExecuter sei , int maxQueueSize ) {
114159 this .maxConcurrent = maxConcurrent ;
115160 this .maxResponseSize = maxResponseSize ;
116161 this .ssi = sei .getThreadScheduler ();
117162 this .sei = sei ;
163+ if (maxQueueSize < 1 || maxQueueSize == Integer .MAX_VALUE ) {
164+ queue = new ConcurrentLinkedQueue <>();
165+ } else {
166+ queue = new ArrayBlockingQueue <>(maxQueueSize );
167+ }
118168 runSocketTask = new RunSocket (ssi );
119169 }
120170
@@ -232,19 +282,7 @@ public HTTPResponseData request(final URL url) throws HTTPParsingException {
232282 * @throws HTTPParsingException is thrown if the server sends back protocol or a response that is larger then allowed.
233283 */
234284 public HTTPResponseData request (final URL url , final HTTPRequestMethod rm , final ByteBuffer bb ) throws HTTPParsingException {
235- HTTPResponseData hr = null ;
236- try {
237- hr = requestAsync (url , rm , bb ).get ();
238- } catch (InterruptedException e ) {
239- Thread .currentThread ().interrupt ();
240- } catch (Exception e ) {
241- if (e .getCause () instanceof HTTPParsingException ) {
242- throw (HTTPParsingException )e .getCause ();
243- } else {
244- throw new HTTPParsingException (e );
245- }
246- }
247- return hr ;
285+ return extractAsyncResponse (requestAsync (url , rm , bb ));
248286 }
249287
250288 /**
@@ -255,21 +293,27 @@ public HTTPResponseData request(final URL url, final HTTPRequestMethod rm, final
255293 * @throws HTTPParsingException is thrown if the server sends back protocol or a response that is larger then allowed.
256294 */
257295 public HTTPResponseData request (final ClientHTTPRequest request ) throws HTTPParsingException {
258- HTTPResponseData hr = null ;
296+ return extractAsyncResponse (requestAsync (request ));
297+ }
298+
299+ protected HTTPResponseData extractAsyncResponse (ListenableFuture <HTTPResponseData > lf ) throws HTTPParsingException {
259300 try {
260- hr = requestAsync ( request ) .get ();
301+ return lf .get ();
261302 } catch (InterruptedException e ) {
262303 Thread .currentThread ().interrupt ();
263- } catch (Exception e ) {
264- if (e .getCause () instanceof HTTPParsingException ) {
304+ lf .cancel (true );
305+ throw new RuntimeException ("Request interrupted" , e );
306+ } catch (CancellationException e ) {
307+ throw e ;
308+ } catch (ExecutionException e ) {
309+ if (e .getCause () instanceof HTTPParsingException ) {
265310 throw (HTTPParsingException )e .getCause ();
266- } else if ( e instanceof CancellationException ) {
267- throw new HTTPParsingException ( "HTTP Timeout!" , e );
311+ } else if ( e . getCause () instanceof RejectedExecutionException ) {
312+ throw ( RejectedExecutionException ) e . getCause ( );
268313 } else {
269314 throw new HTTPParsingException (e );
270315 }
271316 }
272- return hr ;
273317 }
274318
275319 /**
@@ -312,7 +356,9 @@ public ListenableFuture<HTTPResponseData> requestAsync(final URL url, final HTTP
312356 */
313357 public ListenableFuture <HTTPResponseData > requestAsync (final ClientHTTPRequest request ) {
314358 HTTPRequestWrapper hrw = new HTTPRequestWrapper (request );
315- queue .add (hrw );
359+ if (! queue .offer (hrw )) {
360+ return FutureUtils .immediateFailureFuture (new RejectedExecutionException ("Request queue full" ));
361+ }
316362 if (ntse != null ) {
317363 ntse .wakeup ();
318364 runSocketTask .signalToRun ();
0 commit comments