View Javadoc

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