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