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