View Javadoc

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