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.ClientConnectionFactory;
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         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 absolute URI in string format.
412      *
413      * @param uri the request absolute URI
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 absolute URI.
423      *
424      * @param uri the request absolute URI
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 name = field.getName();
462             String value = field.getValue();
463             if (!newRequest.getHeaders().contains(name, value))
464                 newRequest.header(name, value);
465         }
466         return newRequest;
467     }
468 
469     protected HttpRequest newHttpRequest(HttpConversation conversation, URI uri)
470     {
471         return new HttpRequest(this, conversation, checkHost(uri));
472     }
473 
474     /**
475      * <p>Checks {@code uri} for the host to be non-null host.</p>
476      * <p>URIs built from strings that have an internationalized domain name (IDN)
477      * are parsed without errors, but {@code uri.getHost()} returns null.</p>
478      *
479      * @param uri the URI to check for non-null host
480      * @return the same {@code uri} if the host is non-null
481      * @throws IllegalArgumentException if the host is null
482      */
483     private URI checkHost(URI uri)
484     {
485         if (uri.getHost() == null)
486             throw new IllegalArgumentException(String.format("Invalid URI host: null (authority: %s)", uri.getRawAuthority()));
487         return uri;
488     }
489 
490     /**
491      * Returns a {@link Destination} for the given scheme, host and port.
492      * Applications may use {@link Destination}s to create {@link Connection}s
493      * that will be outside {@link HttpClient}'s pooling mechanism, to explicitly
494      * control the connection lifecycle (in particular their termination with
495      * {@link Connection#close()}).
496      *
497      * @param scheme the destination scheme
498      * @param host the destination host
499      * @param port the destination port
500      * @return the destination
501      * @see #getDestinations()
502      */
503     public Destination getDestination(String scheme, String host, int port)
504     {
505         return destinationFor(scheme, host, port);
506     }
507 
508     protected HttpDestination destinationFor(String scheme, String host, int port)
509     {
510         if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme))
511             throw new IllegalArgumentException("Invalid protocol " + scheme);
512 
513         scheme = scheme.toLowerCase(Locale.ENGLISH);
514         host = host.toLowerCase(Locale.ENGLISH);
515         port = normalizePort(scheme, port);
516 
517         Origin origin = new Origin(scheme, host, port);
518         HttpDestination destination = destinations.get(origin);
519         if (destination == null)
520         {
521             destination = transport.newHttpDestination(origin);
522             addManaged(destination);
523             HttpDestination existing = destinations.putIfAbsent(origin, destination);
524             if (existing != null)
525             {
526                 removeBean(destination);
527                 destination = existing;
528             }
529             else
530             {
531                 if (LOG.isDebugEnabled())
532                     LOG.debug("Created {}", destination);
533             }
534         }
535         return destination;
536     }
537 
538     protected boolean removeDestination(HttpDestination destination)
539     {
540         removeBean(destination);
541         return destinations.remove(destination.getOrigin()) != null;
542     }
543 
544     /**
545      * @return the list of destinations known to this {@link HttpClient}.
546      */
547     public List<Destination> getDestinations()
548     {
549         return new ArrayList<>(destinations.values());
550     }
551 
552     protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
553     {
554         HttpDestination destination = destinationFor(request.getScheme(), request.getHost(), request.getPort());
555         destination.send(request, listeners);
556     }
557 
558     protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
559     {
560         Origin.Address address = destination.getConnectAddress();
561         resolver.resolve(address.getHost(), address.getPort(), new Promise<List<InetSocketAddress>>()
562         {
563             @Override
564             public void succeeded(List<InetSocketAddress> socketAddresses)
565             {
566                 Map<String, Object> context = new HashMap<>();
567                 context.put(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, HttpClient.this);
568                 context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
569                 connect(socketAddresses, 0, context);
570             }
571 
572             @Override
573             public void failed(Throwable x)
574             {
575                 promise.failed(x);
576             }
577 
578             private void connect(List<InetSocketAddress> socketAddresses, int index, Map<String, Object> context)
579             {
580                 context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<Connection>(promise)
581                 {
582                     @Override
583                     public void failed(Throwable x)
584                     {
585                         int nextIndex = index + 1;
586                         if (nextIndex == socketAddresses.size())
587                             super.failed(x);
588                         else
589                             connect(socketAddresses, nextIndex, context);
590                     }
591                 });
592                 transport.connect(socketAddresses.get(index), context);
593             }
594         });
595     }
596 
597     private HttpConversation newConversation()
598     {
599         return new HttpConversation();
600     }
601 
602     public ProtocolHandlers getProtocolHandlers()
603     {
604         return handlers;
605     }
606 
607     protected ProtocolHandler findProtocolHandler(Request request, Response response)
608     {
609         return handlers.find(request, response);
610     }
611 
612     /**
613      * @return the {@link ByteBufferPool} of this {@link HttpClient}
614      */
615     public ByteBufferPool getByteBufferPool()
616     {
617         return byteBufferPool;
618     }
619 
620     /**
621      * @param byteBufferPool the {@link ByteBufferPool} of this {@link HttpClient}
622      */
623     public void setByteBufferPool(ByteBufferPool byteBufferPool)
624     {
625         this.byteBufferPool = byteBufferPool;
626     }
627 
628     /**
629      * @return the max time, in milliseconds, a connection can take to connect to destinations
630      */
631     @ManagedAttribute("The timeout, in milliseconds, for connect() operations")
632     public long getConnectTimeout()
633     {
634         return connectTimeout;
635     }
636 
637     /**
638      * @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations
639      * @see java.net.Socket#connect(SocketAddress, int)
640      */
641     public void setConnectTimeout(long connectTimeout)
642     {
643         this.connectTimeout = connectTimeout;
644     }
645 
646     /**
647      * @return the timeout, in milliseconds, for the default {@link SocketAddressResolver} created at startup
648      * @see #getSocketAddressResolver()
649      */
650     public long getAddressResolutionTimeout()
651     {
652         return addressResolutionTimeout;
653     }
654 
655     /**
656      * <p>Sets the socket address resolution timeout used by the default {@link SocketAddressResolver}
657      * created by this {@link HttpClient} at startup.</p>
658      * <p>For more fine tuned configuration of socket address resolution, see
659      * {@link #setSocketAddressResolver(SocketAddressResolver)}.</p>
660      *
661      * @param addressResolutionTimeout the timeout, in milliseconds, for the default {@link SocketAddressResolver} created at startup
662      * @see #setSocketAddressResolver(SocketAddressResolver)
663      */
664     public void setAddressResolutionTimeout(long addressResolutionTimeout)
665     {
666         this.addressResolutionTimeout = addressResolutionTimeout;
667     }
668 
669     /**
670      * @return the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
671      */
672     @ManagedAttribute("The timeout, in milliseconds, to close idle connections")
673     public long getIdleTimeout()
674     {
675         return idleTimeout;
676     }
677 
678     /**
679      * @param idleTimeout the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
680      */
681     public void setIdleTimeout(long idleTimeout)
682     {
683         this.idleTimeout = idleTimeout;
684     }
685 
686     /**
687      * @return the address to bind socket channels to
688      * @see #setBindAddress(SocketAddress)
689      */
690     public SocketAddress getBindAddress()
691     {
692         return bindAddress;
693     }
694 
695     /**
696      * @param bindAddress the address to bind socket channels to
697      * @see #getBindAddress()
698      * @see SocketChannel#bind(SocketAddress)
699      */
700     public void setBindAddress(SocketAddress bindAddress)
701     {
702         this.bindAddress = bindAddress;
703     }
704 
705     /**
706      * @return the "User-Agent" HTTP field of this {@link HttpClient}
707      */
708     public HttpField getUserAgentField()
709     {
710         return agentField;
711     }
712 
713     /**
714      * @param agent the "User-Agent" HTTP header string of this {@link HttpClient}
715      */
716     public void setUserAgentField(HttpField agent)
717     {
718         if (agent.getHeader() != HttpHeader.USER_AGENT)
719             throw new IllegalArgumentException();
720         this.agentField = agent;
721     }
722 
723     /**
724      * @return whether this {@link HttpClient} follows HTTP redirects
725      * @see Request#isFollowRedirects()
726      */
727     @ManagedAttribute("Whether HTTP redirects are followed")
728     public boolean isFollowRedirects()
729     {
730         return followRedirects;
731     }
732 
733     /**
734      * @param follow whether this {@link HttpClient} follows HTTP redirects
735      * @see #setMaxRedirects(int)
736      */
737     public void setFollowRedirects(boolean follow)
738     {
739         this.followRedirects = follow;
740     }
741 
742     /**
743      * @return the {@link Executor} of this {@link HttpClient}
744      */
745     public Executor getExecutor()
746     {
747         return executor;
748     }
749 
750     /**
751      * @param executor the {@link Executor} of this {@link HttpClient}
752      */
753     public void setExecutor(Executor executor)
754     {
755         this.executor = executor;
756     }
757 
758     /**
759      * @return the {@link Scheduler} of this {@link HttpClient}
760      */
761     public Scheduler getScheduler()
762     {
763         return scheduler;
764     }
765 
766     /**
767      * @param scheduler the {@link Scheduler} of this {@link HttpClient}
768      */
769     public void setScheduler(Scheduler scheduler)
770     {
771         this.scheduler = scheduler;
772     }
773 
774     /**
775      * @return the {@link SocketAddressResolver} of this {@link HttpClient}
776      */
777     public SocketAddressResolver getSocketAddressResolver()
778     {
779         return resolver;
780     }
781 
782     /**
783      * @param resolver the {@link SocketAddressResolver} of this {@link HttpClient}
784      */
785     public void setSocketAddressResolver(SocketAddressResolver resolver)
786     {
787         this.resolver = resolver;
788     }
789 
790     /**
791      * @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s
792      */
793     @ManagedAttribute("The max number of connections per each destination")
794     public int getMaxConnectionsPerDestination()
795     {
796         return maxConnectionsPerDestination;
797     }
798 
799     /**
800      * Sets the max number of connections to open to each destinations.
801      * <p>
802      * RFC 2616 suggests that 2 connections should be opened per each destination,
803      * but browsers commonly open 6.
804      * If this {@link HttpClient} is used for load testing, it is common to have only one destination
805      * (the server to load test), and it is recommended to set this value to a high value (at least as
806      * much as the threads present in the {@link #getExecutor() executor}).
807      *
808      * @param maxConnectionsPerDestination the max number of connections that this {@link HttpClient} opens to {@link Destination}s
809      */
810     public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination)
811     {
812         this.maxConnectionsPerDestination = maxConnectionsPerDestination;
813     }
814 
815     /**
816      * @return the max number of requests that may be queued to a {@link Destination}.
817      */
818     @ManagedAttribute("The max number of requests queued per each destination")
819     public int getMaxRequestsQueuedPerDestination()
820     {
821         return maxRequestsQueuedPerDestination;
822     }
823 
824     /**
825      * Sets the max number of requests that may be queued to a destination.
826      * <p>
827      * If this {@link HttpClient} performs a high rate of requests to a destination,
828      * and all the connections managed by that destination are busy with other requests,
829      * then new requests will be queued up in the destination.
830      * This parameter controls how many requests can be queued before starting to reject them.
831      * If this {@link HttpClient} is used for load testing, it is common to have this parameter
832      * set to a high value, although this may impact latency (requests sit in the queue for a long
833      * time before being sent).
834      *
835      * @param maxRequestsQueuedPerDestination the max number of requests that may be queued to a {@link Destination}.
836      */
837     public void setMaxRequestsQueuedPerDestination(int maxRequestsQueuedPerDestination)
838     {
839         this.maxRequestsQueuedPerDestination = maxRequestsQueuedPerDestination;
840     }
841 
842     /**
843      * @return the size of the buffer used to write requests
844      */
845     @ManagedAttribute("The request buffer size")
846     public int getRequestBufferSize()
847     {
848         return requestBufferSize;
849     }
850 
851     /**
852      * @param requestBufferSize the size of the buffer used to write requests
853      */
854     public void setRequestBufferSize(int requestBufferSize)
855     {
856         this.requestBufferSize = requestBufferSize;
857     }
858 
859     /**
860      * @return the size of the buffer used to read responses
861      */
862     @ManagedAttribute("The response buffer size")
863     public int getResponseBufferSize()
864     {
865         return responseBufferSize;
866     }
867 
868     /**
869      * @param responseBufferSize the size of the buffer used to read responses
870      */
871     public void setResponseBufferSize(int responseBufferSize)
872     {
873         this.responseBufferSize = responseBufferSize;
874     }
875 
876     /**
877      * @return the max number of HTTP redirects that are followed
878      * @see #setMaxRedirects(int)
879      */
880     public int getMaxRedirects()
881     {
882         return maxRedirects;
883     }
884 
885     /**
886      * @param maxRedirects the max number of HTTP redirects that are followed
887      * @see #setFollowRedirects(boolean)
888      */
889     public void setMaxRedirects(int maxRedirects)
890     {
891         this.maxRedirects = maxRedirects;
892     }
893 
894     /**
895      * @return whether TCP_NODELAY is enabled
896      */
897     @ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay")
898     public boolean isTCPNoDelay()
899     {
900         return tcpNoDelay;
901     }
902 
903     /**
904      * @param tcpNoDelay whether TCP_NODELAY is enabled
905      * @see java.net.Socket#setTcpNoDelay(boolean)
906      */
907     public void setTCPNoDelay(boolean tcpNoDelay)
908     {
909         this.tcpNoDelay = tcpNoDelay;
910     }
911 
912     /**
913      * @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread
914      * @see #setDispatchIO(boolean)
915      */
916     @Deprecated
917     public boolean isDispatchIO()
918     {
919         // TODO this did default to true, so usage needs to be evaluated.
920         return false;
921     }
922 
923     /**
924      * Whether to dispatch I/O operations from the selector thread to a different thread.
925      * <p>
926      * This implementation never blocks on I/O operation, but invokes application callbacks that may
927      * take time to execute or block on other I/O.
928      * If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO}
929      * should be set to true.
930      * If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO}
931      * may be set to false.
932      *
933      * @param dispatchIO true to dispatch I/O operations in a different thread,
934      *                   false to execute them in the selector thread
935      */
936     @Deprecated
937     public void setDispatchIO(boolean dispatchIO)
938     {
939     }
940 
941     /**
942      * @return whether request events must be strictly ordered
943      * @see #setStrictEventOrdering(boolean)
944      */
945     @ManagedAttribute("Whether request/response events must be strictly ordered")
946     public boolean isStrictEventOrdering()
947     {
948         return strictEventOrdering;
949     }
950 
951     /**
952      * Whether request/response events must be strictly ordered with respect to connection usage.
953      * <p>
954      * From the point of view of connection usage, the connection can be reused just before the
955      * "complete" event notified to {@link org.eclipse.jetty.client.api.Response.CompleteListener}s
956      * (but after the "success" event).
957      * <p>
958      * When a request/response exchange is completing, the destination may have another request
959      * queued to be sent to the server.
960      * If the connection for that destination is reused for the second request before the "complete"
961      * event of the first exchange, it may happen that the "begin" event of the second request
962      * happens before the "complete" event of the first exchange.
963      * <p>
964      * Enforcing strict ordering of events so that a "begin" event of a request can never happen
965      * before the "complete" event of the previous exchange comes with the cost of increased
966      * connection usage.
967      * In case of HTTP redirects and strict event ordering, for example, the redirect request will
968      * be forced to open a new connection because it is typically sent from the complete listener
969      * when the connection cannot yet be reused.
970      * When strict event ordering is not enforced, the redirect request will reuse the already
971      * open connection making the system more efficient.
972      * <p>
973      * The default value for this property is {@code false}.
974      *
975      * @param strictEventOrdering whether request/response events must be strictly ordered
976      */
977     public void setStrictEventOrdering(boolean strictEventOrdering)
978     {
979         this.strictEventOrdering = strictEventOrdering;
980     }
981 
982     /**
983      * @return whether destinations that have no connections should be removed
984      * @see #setRemoveIdleDestinations(boolean)
985      */
986     @ManagedAttribute("Whether idle destinations are removed")
987     public boolean isRemoveIdleDestinations()
988     {
989         return removeIdleDestinations;
990     }
991 
992     /**
993      * Whether destinations that have no connections (nor active nor idle) should be removed.
994      * <p>
995      * Applications typically make request to a limited number of destinations so keeping
996      * destinations around is not a problem for the memory or the GC.
997      * However, for applications that hit millions of different destinations (e.g. a spider
998      * bot) it would be useful to be able to remove the old destinations that won't be visited
999      * anymore and leave space for new destinations.
1000      *
1001      * @param removeIdleDestinations whether destinations that have no connections should be removed
1002      * @see org.eclipse.jetty.client.DuplexConnectionPool
1003      */
1004     public void setRemoveIdleDestinations(boolean removeIdleDestinations)
1005     {
1006         this.removeIdleDestinations = removeIdleDestinations;
1007     }
1008 
1009     /**
1010      * @return whether {@code connect()} operations are performed in blocking mode
1011      */
1012     @ManagedAttribute("Whether the connect() operation is blocking")
1013     public boolean isConnectBlocking()
1014     {
1015         return connectBlocking;
1016     }
1017 
1018     /**
1019      * <p>Whether {@code connect()} operations are performed in blocking mode.</p>
1020      * <p>If {@code connect()} are performed in blocking mode, then {@link Socket#connect(SocketAddress, int)}
1021      * will be used to connect to servers.</p>
1022      * <p>Otherwise, {@link SocketChannel#connect(SocketAddress)} will be used in non-blocking mode,
1023      * therefore registering for {@link SelectionKey#OP_CONNECT} and finishing the connect operation
1024      * when the NIO system emits that event.</p>
1025      *
1026      * @param connectBlocking whether {@code connect()} operations are performed in blocking mode
1027      */
1028     public void setConnectBlocking(boolean connectBlocking)
1029     {
1030         this.connectBlocking = connectBlocking;
1031     }
1032 
1033     /**
1034      * @return the forward proxy configuration
1035      */
1036     public ProxyConfiguration getProxyConfiguration()
1037     {
1038         return proxyConfig;
1039     }
1040 
1041     protected HttpField getAcceptEncodingField()
1042     {
1043         return encodingField;
1044     }
1045 
1046     protected String normalizeHost(String host)
1047     {
1048         if (host != null && host.matches("\\[.*\\]"))
1049             return host.substring(1, host.length() - 1);
1050         return host;
1051     }
1052 
1053     public static int normalizePort(String scheme, int port)
1054     {
1055         return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
1056     }
1057 
1058     public boolean isDefaultPort(String scheme, int port)
1059     {
1060         return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
1061     }
1062 
1063     private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
1064     {
1065         private final Set<ContentDecoder.Factory> set = new HashSet<>();
1066 
1067         @Override
1068         public boolean add(ContentDecoder.Factory e)
1069         {
1070             boolean result = set.add(e);
1071             invalidate();
1072             return result;
1073         }
1074 
1075         @Override
1076         public boolean addAll(Collection<? extends ContentDecoder.Factory> c)
1077         {
1078             boolean result = set.addAll(c);
1079             invalidate();
1080             return result;
1081         }
1082 
1083         @Override
1084         public boolean remove(Object o)
1085         {
1086             boolean result = set.remove(o);
1087             invalidate();
1088             return result;
1089         }
1090 
1091         @Override
1092         public boolean removeAll(Collection<?> c)
1093         {
1094             boolean result = set.removeAll(c);
1095             invalidate();
1096             return result;
1097         }
1098 
1099         @Override
1100         public boolean retainAll(Collection<?> c)
1101         {
1102             boolean result = set.retainAll(c);
1103             invalidate();
1104             return result;
1105         }
1106 
1107         @Override
1108         public void clear()
1109         {
1110             set.clear();
1111             invalidate();
1112         }
1113 
1114         @Override
1115         public int size()
1116         {
1117             return set.size();
1118         }
1119 
1120         @Override
1121         public boolean isEmpty()
1122         {
1123             return set.isEmpty();
1124         }
1125 
1126         @Override
1127         public boolean contains(Object o)
1128         {
1129             return set.contains(o);
1130         }
1131 
1132         @Override
1133         public boolean containsAll(Collection<?> c)
1134         {
1135             return set.containsAll(c);
1136         }
1137 
1138         @Override
1139         public Iterator<ContentDecoder.Factory> iterator()
1140         {
1141             final Iterator<ContentDecoder.Factory> iterator = set.iterator();
1142             return new Iterator<ContentDecoder.Factory>()
1143             {
1144                 @Override
1145                 public boolean hasNext()
1146                 {
1147                     return iterator.hasNext();
1148                 }
1149 
1150                 @Override
1151                 public ContentDecoder.Factory next()
1152                 {
1153                     return iterator.next();
1154                 }
1155 
1156                 @Override
1157                 public void remove()
1158                 {
1159                     iterator.remove();
1160                     invalidate();
1161                 }
1162             };
1163         }
1164 
1165         @Override
1166         public Object[] toArray()
1167         {
1168             return set.toArray();
1169         }
1170 
1171         @Override
1172         public <T> T[] toArray(T[] a)
1173         {
1174             return set.toArray(a);
1175         }
1176 
1177         private void invalidate()
1178         {
1179             if (set.isEmpty())
1180             {
1181                 encodingField = null;
1182             }
1183             else
1184             {
1185                 StringBuilder value = new StringBuilder();
1186                 for (Iterator<ContentDecoder.Factory> iterator = set.iterator(); iterator.hasNext();)
1187                 {
1188                     ContentDecoder.Factory decoderFactory = iterator.next();
1189                     value.append(decoderFactory.getEncoding());
1190                     if (iterator.hasNext())
1191                         value.append(",");
1192                 }
1193                 encodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value.toString());
1194             }
1195         }
1196     }
1197 }