View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client;
20  
21  import java.io.IOException;
22  import java.net.CookieManager;
23  import java.net.CookiePolicy;
24  import java.net.CookieStore;
25  import java.net.SocketAddress;
26  import java.net.URI;
27  import java.nio.channels.SocketChannel;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.Map;
36  import java.util.Objects;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.concurrent.ConcurrentMap;
40  import java.util.concurrent.ExecutionException;
41  import java.util.concurrent.Executor;
42  import java.util.concurrent.TimeUnit;
43  import java.util.concurrent.TimeoutException;
44  
45  import org.eclipse.jetty.client.api.AuthenticationStore;
46  import org.eclipse.jetty.client.api.Connection;
47  import org.eclipse.jetty.client.api.ContentResponse;
48  import org.eclipse.jetty.client.api.Destination;
49  import org.eclipse.jetty.client.api.Request;
50  import org.eclipse.jetty.client.api.Response;
51  import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
52  import org.eclipse.jetty.client.util.FormContentProvider;
53  import org.eclipse.jetty.http.HttpField;
54  import org.eclipse.jetty.http.HttpHeader;
55  import org.eclipse.jetty.http.HttpMethod;
56  import org.eclipse.jetty.http.HttpScheme;
57  import org.eclipse.jetty.io.ByteBufferPool;
58  import org.eclipse.jetty.io.MappedByteBufferPool;
59  import org.eclipse.jetty.util.Fields;
60  import org.eclipse.jetty.util.Jetty;
61  import org.eclipse.jetty.util.Promise;
62  import org.eclipse.jetty.util.SocketAddressResolver;
63  import org.eclipse.jetty.util.component.ContainerLifeCycle;
64  import org.eclipse.jetty.util.log.Log;
65  import org.eclipse.jetty.util.log.Logger;
66  import org.eclipse.jetty.util.ssl.SslContextFactory;
67  import org.eclipse.jetty.util.thread.QueuedThreadPool;
68  import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
69  import org.eclipse.jetty.util.thread.Scheduler;
70  
71  /**
72   * <p>{@link HttpClient} provides an efficient, asynchronous, non-blocking implementation
73   * to perform HTTP requests to a server through a simple API that offers also blocking semantic.</p>
74   * <p>{@link HttpClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP
75   * requests in a one-liner, but also gives the ability to fine tune the configuration of requests via
76   * {@link HttpClient#newRequest(URI)}.</p>
77   * <p>{@link HttpClient} acts as a central configuration point for network parameters (such as idle timeouts)
78   * and HTTP parameters (such as whether to follow redirects).</p>
79   * <p>{@link HttpClient} transparently pools connections to servers, but allows direct control of connections
80   * for cases where this is needed.</p>
81   * <p>{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.</p>
82   * <p>Typical usage:</p>
83   * <pre>
84   * HttpClient httpClient = new HttpClient();
85   * httpClient.start();
86   *
87   * // One liner:
88   * httpClient.GET("http://localhost:8080/").get().status();
89   *
90   * // Building a request with a timeout
91   * Response response = httpClient.newRequest("http://localhost:8080").send().get(5, TimeUnit.SECONDS);
92   * int status = response.status();
93   *
94   * // Asynchronously
95   * httpClient.newRequest("http://localhost:8080").send(new Response.CompleteListener()
96   * {
97   *     &#64;Override
98   *     public void onComplete(Result result)
99   *     {
100  *         ...
101  *     }
102  * });
103  * </pre>
104  */
105 public class HttpClient extends ContainerLifeCycle
106 {
107     private static final Logger LOG = Log.getLogger(HttpClient.class);
108 
109     private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
110     private final List<ProtocolHandler> handlers = new ArrayList<>();
111     private final List<Request.Listener> requestListeners = new ArrayList<>();
112     private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
113     private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
114     private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
115     private final HttpClientTransport transport;
116     private final SslContextFactory sslContextFactory;
117     private volatile CookieManager cookieManager;
118     private volatile CookieStore cookieStore;
119     private volatile Executor executor;
120     private volatile ByteBufferPool byteBufferPool;
121     private volatile Scheduler scheduler;
122     private volatile SocketAddressResolver resolver;
123     private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
124     private volatile boolean followRedirects = true;
125     private volatile int maxConnectionsPerDestination = 64;
126     private volatile int maxRequestsQueuedPerDestination = 1024;
127     private volatile int requestBufferSize = 4096;
128     private volatile int responseBufferSize = 16384;
129     private volatile int maxRedirects = 8;
130     private volatile SocketAddress bindAddress;
131     private volatile long connectTimeout = 15000;
132     private volatile long addressResolutionTimeout = 15000;
133     private volatile long idleTimeout;
134     private volatile boolean tcpNoDelay = true;
135     private volatile boolean dispatchIO = true;
136     private volatile boolean strictEventOrdering = false;
137     private volatile HttpField encodingField;
138     private volatile boolean removeIdleDestinations = false;
139 
140     /**
141      * Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
142      * (that is, requests with the "http" scheme only, and not "https").
143      *
144      * @see #HttpClient(SslContextFactory) to perform requests to TLS destinations.
145      */
146     public HttpClient()
147     {
148         this(null);
149     }
150 
151     /**
152      * Creates a {@link HttpClient} instance that can perform requests to non-TLS and TLS destinations
153      * (that is, both requests with the "http" scheme and with the "https" scheme).
154      *
155      * @param sslContextFactory the {@link SslContextFactory} that manages TLS encryption
156      * @see #getSslContextFactory()
157      */
158     public HttpClient(SslContextFactory sslContextFactory)
159     {
160         this(new HttpClientTransportOverHTTP(), sslContextFactory);
161     }
162 
163     public HttpClient(HttpClientTransport transport, SslContextFactory sslContextFactory)
164     {
165         this.transport = transport;
166         this.sslContextFactory = sslContextFactory;
167     }
168 
169     public HttpClientTransport getTransport()
170     {
171         return transport;
172     }
173 
174     /**
175      * @return the {@link SslContextFactory} that manages TLS encryption
176      * @see #HttpClient(SslContextFactory)
177      */
178     public SslContextFactory getSslContextFactory()
179     {
180         return sslContextFactory;
181     }
182 
183     @Override
184     protected void doStart() throws Exception
185     {
186         if (sslContextFactory != null)
187             addBean(sslContextFactory);
188 
189         String name = HttpClient.class.getSimpleName() + "@" + hashCode();
190 
191         if (executor == null)
192         {
193             QueuedThreadPool threadPool = new QueuedThreadPool();
194             threadPool.setName(name);
195             executor = threadPool;
196         }
197         addBean(executor);
198 
199         if (byteBufferPool == null)
200             byteBufferPool = new MappedByteBufferPool();
201         addBean(byteBufferPool);
202 
203         if (scheduler == null)
204             scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
205         addBean(scheduler);
206 
207         transport.setHttpClient(this);
208         addBean(transport);
209 
210         resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
211 
212         handlers.add(new ContinueProtocolHandler(this));
213         handlers.add(new RedirectProtocolHandler(this));
214         handlers.add(new WWWAuthenticationProtocolHandler(this));
215         handlers.add(new ProxyAuthenticationProtocolHandler(this));
216 
217         decoderFactories.add(new GZIPContentDecoder.Factory());
218 
219         cookieManager = newCookieManager();
220         cookieStore = cookieManager.getCookieStore();
221 
222         super.doStart();
223     }
224 
225     private CookieManager newCookieManager()
226     {
227         return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL);
228     }
229 
230     @Override
231     protected void doStop() throws Exception
232     {
233         cookieStore.removeAll();
234         decoderFactories.clear();
235         handlers.clear();
236 
237         for (HttpDestination destination : destinations.values())
238             destination.close();
239         destinations.clear();
240 
241         requestListeners.clear();
242         authenticationStore.clearAuthentications();
243         authenticationStore.clearAuthenticationResults();
244 
245         super.doStop();
246     }
247 
248     /**
249      * Returns a <em>non</em> thread-safe list of {@link org.eclipse.jetty.client.api.Request.Listener}s that can be modified before
250      * performing requests.
251      *
252      * @return a list of {@link org.eclipse.jetty.client.api.Request.Listener} that can be used to add and remove listeners
253      */
254     public List<Request.Listener> getRequestListeners()
255     {
256         return requestListeners;
257     }
258 
259     /**
260      * @return the cookie store associated with this instance
261      */
262     public CookieStore getCookieStore()
263     {
264         return cookieStore;
265     }
266 
267     /**
268      * @param cookieStore the cookie store associated with this instance
269      */
270     public void setCookieStore(CookieStore cookieStore)
271     {
272         this.cookieStore = Objects.requireNonNull(cookieStore);
273         this.cookieManager = newCookieManager();
274     }
275 
276     /**
277      * Keep this method package-private because its interface is so ugly
278      * that we really don't want to expose it more than strictly needed.
279      *
280      * @return the cookie manager
281      */
282     CookieManager getCookieManager()
283     {
284         return cookieManager;
285     }
286 
287     /**
288      * @return the authentication store associated with this instance
289      */
290     public AuthenticationStore getAuthenticationStore()
291     {
292         return authenticationStore;
293     }
294 
295     /**
296      * Returns a <em>non</em> thread-safe set of {@link ContentDecoder.Factory}s that can be modified before
297      * performing requests.
298      *
299      * @return a set of {@link ContentDecoder.Factory} that can be used to add and remove content decoder factories
300      */
301     public Set<ContentDecoder.Factory> getContentDecoderFactories()
302     {
303         return decoderFactories;
304     }
305 
306     /**
307      * Performs a GET request to the specified URI.
308      *
309      * @param uri the URI to GET
310      * @return the {@link ContentResponse} for the request
311      * @see #GET(URI)
312      */
313     public ContentResponse GET(String uri) throws InterruptedException, ExecutionException, TimeoutException
314     {
315         return GET(URI.create(uri));
316     }
317 
318     /**
319      * Performs a GET request to the specified URI.
320      *
321      * @param uri the URI to GET
322      * @return the {@link ContentResponse} for the request
323      * @see #newRequest(URI)
324      */
325     public ContentResponse GET(URI uri) throws InterruptedException, ExecutionException, TimeoutException
326     {
327         return newRequest(uri).send();
328     }
329 
330     /**
331      * Performs a POST request to the specified URI with the given form parameters.
332      *
333      * @param uri the URI to POST
334      * @param fields the fields composing the form name/value pairs
335      * @return the {@link ContentResponse} for the request
336      */
337     public ContentResponse FORM(String uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
338     {
339         return FORM(URI.create(uri), fields);
340     }
341 
342     /**
343      * Performs a POST request to the specified URI with the given form parameters.
344      *
345      * @param uri the URI to POST
346      * @param fields the fields composing the form name/value pairs
347      * @return the {@link ContentResponse} for the request
348      */
349     public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
350     {
351         return POST(uri).content(new FormContentProvider(fields)).send();
352     }
353 
354     /**
355      * Creates a POST request to the specified URI.
356      *
357      * @param uri the URI to POST to
358      * @return the POST request
359      * @see #POST(URI)
360      */
361     public Request POST(String uri)
362     {
363         return POST(URI.create(uri));
364     }
365 
366     /**
367      * Creates a POST request to the specified URI.
368      *
369      * @param uri the URI to POST to
370      * @return the POST request
371      */
372     public Request POST(URI uri)
373     {
374         return newRequest(uri).method(HttpMethod.POST);
375     }
376 
377     /**
378      * Creates a new request with the "http" scheme and the specified host and port
379      *
380      * @param host the request host
381      * @param port the request port
382      * @return the request just created
383      */
384     public Request newRequest(String host, int port)
385     {
386         return newRequest(new Origin("http", host, port).asString());
387     }
388 
389     /**
390      * Creates a new request with the specified URI.
391      *
392      * @param uri the URI to request
393      * @return the request just created
394      */
395     public Request newRequest(String uri)
396     {
397         return newRequest(URI.create(uri));
398     }
399 
400     /**
401      * Creates a new request with the specified URI.
402      *
403      * @param uri the URI to request
404      * @return the request just created
405      */
406     public Request newRequest(URI uri)
407     {
408         return newHttpRequest(newConversation(), uri);
409     }
410 
411     protected Request copyRequest(HttpRequest oldRequest, URI newURI)
412     {
413         Request newRequest = newHttpRequest(oldRequest.getConversation(), newURI);
414         newRequest.method(oldRequest.getMethod())
415                 .version(oldRequest.getVersion())
416                 .content(oldRequest.getContent())
417                 .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
418                 .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
419                 .followRedirects(oldRequest.isFollowRedirects());
420         for (HttpField field : oldRequest.getHeaders())
421         {
422             HttpHeader header = field.getHeader();
423             // We have a new URI, so skip the host header if present.
424             if (HttpHeader.HOST == header)
425                 continue;
426 
427             // Remove expectation headers.
428             if (HttpHeader.EXPECT == header)
429                 continue;
430 
431             // Remove cookies.
432             if (HttpHeader.COOKIE == header)
433                 continue;
434 
435             // Remove authorization headers.
436             if (HttpHeader.AUTHORIZATION == header ||
437                     HttpHeader.PROXY_AUTHORIZATION == header)
438                 continue;
439 
440             String value = field.getValue();
441             if (!newRequest.getHeaders().contains(header, value))
442                 newRequest.header(field.getName(), value);
443         }
444         return newRequest;
445     }
446 
447     protected HttpRequest newHttpRequest(HttpConversation conversation, URI uri)
448     {
449         return new HttpRequest(this, conversation, uri);
450     }
451 
452     /**
453      * Returns a {@link Destination} for the given scheme, host and port.
454      * Applications may use {@link Destination}s to create {@link Connection}s
455      * that will be outside {@link HttpClient}'s pooling mechanism, to explicitly
456      * control the connection lifecycle (in particular their termination with
457      * {@link Connection#close()}).
458      *
459      * @param scheme the destination scheme
460      * @param host the destination host
461      * @param port the destination port
462      * @return the destination
463      * @see #getDestinations()
464      */
465     public Destination getDestination(String scheme, String host, int port)
466     {
467         return destinationFor(scheme, host, port);
468     }
469 
470     protected HttpDestination destinationFor(String scheme, String host, int port)
471     {
472         port = normalizePort(scheme, port);
473 
474         Origin origin = new Origin(scheme, host, port);
475         HttpDestination destination = destinations.get(origin);
476         if (destination == null)
477         {
478             destination = transport.newHttpDestination(origin);
479             if (isRunning())
480             {
481                 HttpDestination existing = destinations.putIfAbsent(origin, destination);
482                 if (existing != null)
483                     destination = existing;
484                 else
485                     LOG.debug("Created {}", destination);
486                 if (!isRunning())
487                     destinations.remove(origin);
488             }
489 
490         }
491         return destination;
492     }
493 
494     protected boolean removeDestination(HttpDestination destination)
495     {
496         return destinations.remove(destination.getOrigin()) != null;
497     }
498 
499     /**
500      * @return the list of destinations known to this {@link HttpClient}.
501      */
502     public List<Destination> getDestinations()
503     {
504         return new ArrayList<Destination>(destinations.values());
505     }
506 
507     protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
508     {
509         String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
510         if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme))
511             throw new IllegalArgumentException("Invalid protocol " + scheme);
512 
513         HttpDestination destination = destinationFor(scheme, request.getHost(), request.getPort());
514         destination.send(request, listeners);
515     }
516 
517     protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
518     {
519         Origin.Address address = destination.getConnectAddress();
520         resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
521         {
522             @Override
523             public void succeeded(SocketAddress socketAddress)
524             {
525                 Map<String, Object> context = new HashMap<>();
526                 context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
527                 context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
528                 transport.connect(socketAddress, context);
529             }
530 
531             @Override
532             public void failed(Throwable x)
533             {
534                 promise.failed(x);
535             }
536         });
537     }
538 
539     private HttpConversation newConversation()
540     {
541         return new HttpConversation();
542     }
543 
544     protected List<ProtocolHandler> getProtocolHandlers()
545     {
546         return handlers;
547     }
548 
549     protected ProtocolHandler findProtocolHandler(Request request, Response response)
550     {
551         // Optimized to avoid allocations of iterator instances
552         List<ProtocolHandler> protocolHandlers = getProtocolHandlers();
553         for (int i = 0; i < protocolHandlers.size(); ++i)
554         {
555             ProtocolHandler handler = protocolHandlers.get(i);
556             if (handler.accept(request, response))
557                 return handler;
558         }
559         return null;
560     }
561 
562     /**
563      * @return the {@link ByteBufferPool} of this {@link HttpClient}
564      */
565     public ByteBufferPool getByteBufferPool()
566     {
567         return byteBufferPool;
568     }
569 
570     /**
571      * @param byteBufferPool the {@link ByteBufferPool} of this {@link HttpClient}
572      */
573     public void setByteBufferPool(ByteBufferPool byteBufferPool)
574     {
575         this.byteBufferPool = byteBufferPool;
576     }
577 
578     /**
579      * @return the max time, in milliseconds, a connection can take to connect to destinations
580      */
581     public long getConnectTimeout()
582     {
583         return connectTimeout;
584     }
585 
586     /**
587      * @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations
588      * @see java.net.Socket#connect(SocketAddress, int)
589      */
590     public void setConnectTimeout(long connectTimeout)
591     {
592         this.connectTimeout = connectTimeout;
593     }
594 
595     /**
596      * @return the timeout, in milliseconds, for the DNS resolution of host addresses
597      */
598     public long getAddressResolutionTimeout()
599     {
600         return addressResolutionTimeout;
601     }
602 
603     /**
604      * @param addressResolutionTimeout the timeout, in milliseconds, for the DNS resolution of host addresses
605      */
606     public void setAddressResolutionTimeout(long addressResolutionTimeout)
607     {
608         this.addressResolutionTimeout = addressResolutionTimeout;
609     }
610 
611     /**
612      * @return the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
613      */
614     public long getIdleTimeout()
615     {
616         return idleTimeout;
617     }
618 
619     /**
620      * @param idleTimeout the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
621      */
622     public void setIdleTimeout(long idleTimeout)
623     {
624         this.idleTimeout = idleTimeout;
625     }
626 
627     /**
628      * @return the address to bind socket channels to
629      * @see #setBindAddress(SocketAddress)
630      */
631     public SocketAddress getBindAddress()
632     {
633         return bindAddress;
634     }
635 
636     /**
637      * @param bindAddress the address to bind socket channels to
638      * @see #getBindAddress()
639      * @see SocketChannel#bind(SocketAddress)
640      */
641     public void setBindAddress(SocketAddress bindAddress)
642     {
643         this.bindAddress = bindAddress;
644     }
645 
646     /**
647      * @return the "User-Agent" HTTP field of this {@link HttpClient}
648      */
649     public HttpField getUserAgentField()
650     {
651         return agentField;
652     }
653 
654     /**
655      * @param agent the "User-Agent" HTTP header string of this {@link HttpClient}
656      */
657     public void setUserAgentField(HttpField agent)
658     {
659         if (agent.getHeader() != HttpHeader.USER_AGENT)
660             throw new IllegalArgumentException();
661         this.agentField = agent;
662     }
663 
664     /**
665      * @return whether this {@link HttpClient} follows HTTP redirects
666      * @see Request#isFollowRedirects()
667      */
668     public boolean isFollowRedirects()
669     {
670         return followRedirects;
671     }
672 
673     /**
674      * @param follow whether this {@link HttpClient} follows HTTP redirects
675      * @see #setMaxRedirects(int)
676      */
677     public void setFollowRedirects(boolean follow)
678     {
679         this.followRedirects = follow;
680     }
681 
682     /**
683      * @return the {@link Executor} of this {@link HttpClient}
684      */
685     public Executor getExecutor()
686     {
687         return executor;
688     }
689 
690     /**
691      * @param executor the {@link Executor} of this {@link HttpClient}
692      */
693     public void setExecutor(Executor executor)
694     {
695         this.executor = executor;
696     }
697 
698     /**
699      * @return the {@link Scheduler} of this {@link HttpClient}
700      */
701     public Scheduler getScheduler()
702     {
703         return scheduler;
704     }
705 
706     /**
707      * @param scheduler the {@link Scheduler} of this {@link HttpClient}
708      */
709     public void setScheduler(Scheduler scheduler)
710     {
711         this.scheduler = scheduler;
712     }
713 
714     /**
715      * @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s
716      */
717     public int getMaxConnectionsPerDestination()
718     {
719         return maxConnectionsPerDestination;
720     }
721 
722     /**
723      * Sets the max number of connections to open to each destinations.
724      * <p />
725      * RFC 2616 suggests that 2 connections should be opened per each destination,
726      * but browsers commonly open 6.
727      * If this {@link HttpClient} is used for load testing, it is common to have only one destination
728      * (the server to load test), and it is recommended to set this value to a high value (at least as
729      * much as the threads present in the {@link #getExecutor() executor}).
730      *
731      * @param maxConnectionsPerDestination the max number of connections that this {@link HttpClient} opens to {@link Destination}s
732      */
733     public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination)
734     {
735         this.maxConnectionsPerDestination = maxConnectionsPerDestination;
736     }
737 
738     /**
739      * @return the max number of requests that may be queued to a {@link Destination}.
740      */
741     public int getMaxRequestsQueuedPerDestination()
742     {
743         return maxRequestsQueuedPerDestination;
744     }
745 
746     /**
747      * Sets the max number of requests that may be queued to a destination.
748      * <p />
749      * If this {@link HttpClient} performs a high rate of requests to a destination,
750      * and all the connections managed by that destination are busy with other requests,
751      * then new requests will be queued up in the destination.
752      * This parameter controls how many requests can be queued before starting to reject them.
753      * If this {@link HttpClient} is used for load testing, it is common to have this parameter
754      * set to a high value, although this may impact latency (requests sit in the queue for a long
755      * time before being sent).
756      *
757      * @param maxRequestsQueuedPerDestination the max number of requests that may be queued to a {@link Destination}.
758      */
759     public void setMaxRequestsQueuedPerDestination(int maxRequestsQueuedPerDestination)
760     {
761         this.maxRequestsQueuedPerDestination = maxRequestsQueuedPerDestination;
762     }
763 
764     /**
765      * @return the size of the buffer used to write requests
766      */
767     public int getRequestBufferSize()
768     {
769         return requestBufferSize;
770     }
771 
772     /**
773      * @param requestBufferSize the size of the buffer used to write requests
774      */
775     public void setRequestBufferSize(int requestBufferSize)
776     {
777         this.requestBufferSize = requestBufferSize;
778     }
779 
780     /**
781      * @return the size of the buffer used to read responses
782      */
783     public int getResponseBufferSize()
784     {
785         return responseBufferSize;
786     }
787 
788     /**
789      * @param responseBufferSize the size of the buffer used to read responses
790      */
791     public void setResponseBufferSize(int responseBufferSize)
792     {
793         this.responseBufferSize = responseBufferSize;
794     }
795 
796     /**
797      * @return the max number of HTTP redirects that are followed
798      * @see #setMaxRedirects(int)
799      */
800     public int getMaxRedirects()
801     {
802         return maxRedirects;
803     }
804 
805     /**
806      * @param maxRedirects the max number of HTTP redirects that are followed
807      * @see #setFollowRedirects(boolean)
808      */
809     public void setMaxRedirects(int maxRedirects)
810     {
811         this.maxRedirects = maxRedirects;
812     }
813 
814     /**
815      * @return whether TCP_NODELAY is enabled
816      */
817     public boolean isTCPNoDelay()
818     {
819         return tcpNoDelay;
820     }
821 
822     /**
823      * @param tcpNoDelay whether TCP_NODELAY is enabled
824      * @see java.net.Socket#setTcpNoDelay(boolean)
825      */
826     public void setTCPNoDelay(boolean tcpNoDelay)
827     {
828         this.tcpNoDelay = tcpNoDelay;
829     }
830 
831     /**
832      * @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread
833      * @see #setDispatchIO(boolean)
834      */
835     public boolean isDispatchIO()
836     {
837         return dispatchIO;
838     }
839 
840     /**
841      * Whether to dispatch I/O operations from the selector thread to a different thread.
842      * <p />
843      * This implementation never blocks on I/O operation, but invokes application callbacks that may
844      * take time to execute or block on other I/O.
845      * If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO}
846      * should be set to true.
847      * If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO}
848      * may be set to false.
849      *
850      * @param dispatchIO true to dispatch I/O operations in a different thread,
851      *                   false to execute them in the selector thread
852      */
853     public void setDispatchIO(boolean dispatchIO)
854     {
855         this.dispatchIO = dispatchIO;
856     }
857 
858     /**
859      * @return whether request events must be strictly ordered
860      * @see #setStrictEventOrdering(boolean)
861      */
862     public boolean isStrictEventOrdering()
863     {
864         return strictEventOrdering;
865     }
866 
867     /**
868      * Whether request/response events must be strictly ordered with respect to connection usage.
869      * <p />
870      * From the point of view of connection usage, the connection can be reused just before the
871      * "complete" event notified to {@link org.eclipse.jetty.client.api.Response.CompleteListener}s
872      * (but after the "success" event).
873      * <p />
874      * When a request/response exchange is completing, the destination may have another request
875      * queued to be sent to the server.
876      * If the connection for that destination is reused for the second request before the "complete"
877      * event of the first exchange, it may happen that the "begin" event of the second request
878      * happens before the "complete" event of the first exchange.
879      * <p />
880      * Enforcing strict ordering of events so that a "begin" event of a request can never happen
881      * before the "complete" event of the previous exchange comes with the cost of increased
882      * connection usage.
883      * In case of HTTP redirects and strict event ordering, for example, the redirect request will
884      * be forced to open a new connection because it is typically sent from the complete listener
885      * when the connection cannot yet be reused.
886      * When strict event ordering is not enforced, the redirect request will reuse the already
887      * open connection making the system more efficient.
888      * <p />
889      * The default value for this property is {@code false}.
890      *
891      * @param strictEventOrdering whether request/response events must be strictly ordered
892      */
893     public void setStrictEventOrdering(boolean strictEventOrdering)
894     {
895         this.strictEventOrdering = strictEventOrdering;
896     }
897 
898     /**
899      * @return whether destinations that have no connections should be removed
900      * @see #setRemoveIdleDestinations(boolean)
901      */
902     public boolean isRemoveIdleDestinations()
903     {
904         return removeIdleDestinations;
905     }
906 
907     /**
908      * Whether destinations that have no connections (nor active nor idle) should be removed.
909      * <p />
910      * Applications typically make request to a limited number of destinations so keeping
911      * destinations around is not a problem for the memory or the GC.
912      * However, for applications that hit millions of different destinations (e.g. a spider
913      * bot) it would be useful to be able to remove the old destinations that won't be visited
914      * anymore and leave space for new destinations.
915      *
916      * @param removeIdleDestinations whether destinations that have no connections should be removed
917      * @see org.eclipse.jetty.client.ConnectionPool
918      */
919     public void setRemoveIdleDestinations(boolean removeIdleDestinations)
920     {
921         this.removeIdleDestinations = removeIdleDestinations;
922     }
923 
924     /**
925      * @return the forward proxy configuration
926      */
927     public ProxyConfiguration getProxyConfiguration()
928     {
929         return proxyConfig;
930     }
931 
932     protected HttpField getAcceptEncodingField()
933     {
934         return encodingField;
935     }
936 
937     protected String normalizeHost(String host)
938     {
939         if (host != null && host.matches("\\[.*\\]"))
940             return host.substring(1, host.length() - 1);
941         return host;
942     }
943 
944     protected int normalizePort(String scheme, int port)
945     {
946         return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
947     }
948 
949     protected boolean isDefaultPort(String scheme, int port)
950     {
951         return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
952     }
953 
954     @Override
955     public void dump(Appendable out, String indent) throws IOException
956     {
957         dumpThis(out);
958         dump(out, indent, getBeans(), destinations.values());
959     }
960 
961     private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
962     {
963         private final Set<ContentDecoder.Factory> set = new HashSet<>();
964 
965         @Override
966         public boolean add(ContentDecoder.Factory e)
967         {
968             boolean result = set.add(e);
969             invalidate();
970             return result;
971         }
972 
973         @Override
974         public boolean addAll(Collection<? extends ContentDecoder.Factory> c)
975         {
976             boolean result = set.addAll(c);
977             invalidate();
978             return result;
979         }
980 
981         @Override
982         public boolean remove(Object o)
983         {
984             boolean result = set.remove(o);
985             invalidate();
986             return result;
987         }
988 
989         @Override
990         public boolean removeAll(Collection<?> c)
991         {
992             boolean result = set.removeAll(c);
993             invalidate();
994             return result;
995         }
996 
997         @Override
998         public boolean retainAll(Collection<?> c)
999         {
1000             boolean result = set.retainAll(c);
1001             invalidate();
1002             return result;
1003         }
1004 
1005         @Override
1006         public void clear()
1007         {
1008             set.clear();
1009             invalidate();
1010         }
1011 
1012         @Override
1013         public int size()
1014         {
1015             return set.size();
1016         }
1017 
1018         @Override
1019         public boolean isEmpty()
1020         {
1021             return set.isEmpty();
1022         }
1023 
1024         @Override
1025         public boolean contains(Object o)
1026         {
1027             return set.contains(o);
1028         }
1029 
1030         @Override
1031         public boolean containsAll(Collection<?> c)
1032         {
1033             return set.containsAll(c);
1034         }
1035 
1036         @Override
1037         public Iterator<ContentDecoder.Factory> iterator()
1038         {
1039             final Iterator<ContentDecoder.Factory> iterator = set.iterator();
1040             return new Iterator<ContentDecoder.Factory>()
1041             {
1042                 @Override
1043                 public boolean hasNext()
1044                 {
1045                     return iterator.hasNext();
1046                 }
1047 
1048                 @Override
1049                 public ContentDecoder.Factory next()
1050                 {
1051                     return iterator.next();
1052                 }
1053 
1054                 @Override
1055                 public void remove()
1056                 {
1057                     iterator.remove();
1058                     invalidate();
1059                 }
1060             };
1061         }
1062 
1063         @Override
1064         public Object[] toArray()
1065         {
1066             return set.toArray();
1067         }
1068 
1069         @Override
1070         public <T> T[] toArray(T[] a)
1071         {
1072             return set.toArray(a);
1073         }
1074 
1075         private void invalidate()
1076         {
1077             if (set.isEmpty())
1078             {
1079                 encodingField = null;
1080             }
1081             else
1082             {
1083                 StringBuilder value = new StringBuilder();
1084                 for (Iterator<ContentDecoder.Factory> iterator = set.iterator(); iterator.hasNext();)
1085                 {
1086                     ContentDecoder.Factory decoderFactory = iterator.next();
1087                     value.append(decoderFactory.getEncoding());
1088                     if (iterator.hasNext())
1089                         value.append(",");
1090                 }
1091                 encodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value.toString());
1092             }
1093         }
1094     }
1095 }