View Javadoc

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