View Javadoc

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