View Javadoc

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