View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.conn.routing;
29  
30  import java.net.InetAddress;
31  import java.net.InetSocketAddress;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.List;
36  
37  import org.apache.http.HttpHost;
38  import org.apache.http.annotation.Immutable;
39  import org.apache.http.util.Args;
40  import org.apache.http.util.LangUtils;
41  
42  /**
43   * The route for a request.
44   *
45   * @since 4.0
46   */
47  @Immutable
48  public final class HttpRoute implements RouteInfo, Cloneable {
49  
50      /** The target host to connect to. */
51      private final HttpHost targetHost;
52  
53      /**
54       * The local address to connect from.
55       * <code>null</code> indicates that the default should be used.
56       */
57      private final InetAddress localAddress;
58  
59      /** The proxy servers, if any. Never null. */
60      private final List<HttpHost> proxyChain;
61  
62      /** Whether the the route is tunnelled through the proxy. */
63      private final TunnelType tunnelled;
64  
65      /** Whether the route is layered. */
66      private final LayerType layered;
67  
68      /** Whether the route is (supposed to be) secure. */
69      private final boolean secure;
70  
71      private HttpRoute(final HttpHost target, final InetAddress local, final List<HttpHost> proxies,
72                       final boolean secure, final TunnelType tunnelled, final LayerType layered) {
73          Args.notNull(target, "Target host");
74          this.targetHost   = target;
75          this.localAddress = local;
76          if (proxies != null && !proxies.isEmpty()) {
77              this.proxyChain = new ArrayList<HttpHost>(proxies);
78          } else {
79              this.proxyChain = null;
80          }
81          if (tunnelled == TunnelType.TUNNELLED) {
82              Args.check(this.proxyChain != null, "Proxy required if tunnelled");
83          }
84          this.secure       = secure;
85          this.tunnelled    = tunnelled != null ? tunnelled : TunnelType.PLAIN;
86          this.layered      = layered != null ? layered : LayerType.PLAIN;
87      }
88  
89      /**
90       * Creates a new route with all attributes specified explicitly.
91       *
92       * @param target    the host to which to route
93       * @param local     the local address to route from, or
94       *                  <code>null</code> for the default
95       * @param proxies   the proxy chain to use, or
96       *                  <code>null</code> for a direct route
97       * @param secure    <code>true</code> if the route is (to be) secure,
98       *                  <code>false</code> otherwise
99       * @param tunnelled the tunnel type of this route
100      * @param layered   the layering type of this route
101      */
102     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies,
103                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
104         this(target, local, proxies != null ? Arrays.asList(proxies) : null,
105                 secure, tunnelled, layered);
106     }
107 
108     /**
109      * Creates a new route with at most one proxy.
110      *
111      * @param target    the host to which to route
112      * @param local     the local address to route from, or
113      *                  <code>null</code> for the default
114      * @param proxy     the proxy to use, or
115      *                  <code>null</code> for a direct route
116      * @param secure    <code>true</code> if the route is (to be) secure,
117      *                  <code>false</code> otherwise
118      * @param tunnelled <code>true</code> if the route is (to be) tunnelled
119      *                  via the proxy,
120      *                  <code>false</code> otherwise
121      * @param layered   <code>true</code> if the route includes a
122      *                  layered protocol,
123      *                  <code>false</code> otherwise
124      */
125     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
126                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
127         this(target, local, proxy != null ? Collections.singletonList(proxy) : null,
128                 secure, tunnelled, layered);
129     }
130 
131     /**
132      * Creates a new direct route.
133      * That is a route without a proxy.
134      *
135      * @param target    the host to which to route
136      * @param local     the local address to route from, or
137      *                  <code>null</code> for the default
138      * @param secure    <code>true</code> if the route is (to be) secure,
139      *                  <code>false</code> otherwise
140      */
141     public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) {
142         this(target, local, Collections.<HttpHost>emptyList(), secure,
143                 TunnelType.PLAIN, LayerType.PLAIN);
144     }
145 
146     /**
147      * Creates a new direct insecure route.
148      *
149      * @param target    the host to which to route
150      */
151     public HttpRoute(final HttpHost target) {
152         this(target, null, Collections.<HttpHost>emptyList(), false,
153                 TunnelType.PLAIN, LayerType.PLAIN);
154     }
155 
156     /**
157      * Creates a new route through a proxy.
158      * When using this constructor, the <code>proxy</code> MUST be given.
159      * For convenience, it is assumed that a secure connection will be
160      * layered over a tunnel through the proxy.
161      *
162      * @param target    the host to which to route
163      * @param local     the local address to route from, or
164      *                  <code>null</code> for the default
165      * @param proxy     the proxy to use
166      * @param secure    <code>true</code> if the route is (to be) secure,
167      *                  <code>false</code> otherwise
168      */
169     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
170                      final boolean secure) {
171         this(target, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
172              secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
173              secure ? LayerType.LAYERED    : LayerType.PLAIN);
174     }
175 
176     /**
177      * Creates a new plain route through a proxy.
178      *
179      * @param target    the host to which to route
180      * @param proxy     the proxy to use
181      *
182      * @since 4.3
183      */
184     public HttpRoute(final HttpHost target, final HttpHost proxy) {
185         this(target, null, proxy, false);
186     }
187 
188     @Override
189     public final HttpHost getTargetHost() {
190         return this.targetHost;
191     }
192 
193     @Override
194     public final InetAddress getLocalAddress() {
195         return this.localAddress;
196     }
197 
198     public final InetSocketAddress getLocalSocketAddress() {
199         return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
200     }
201 
202     @Override
203     public final int getHopCount() {
204         return proxyChain != null ? proxyChain.size() + 1 : 1;
205     }
206 
207     @Override
208     public final HttpHost getHopTarget(final int hop) {
209         Args.notNegative(hop, "Hop index");
210         final int hopcount = getHopCount();
211         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
212         if (hop < hopcount - 1) {
213             return this.proxyChain.get(hop);
214         } else {
215             return this.targetHost;
216         }
217     }
218 
219     @Override
220     public final HttpHost getProxyHost() {
221         return proxyChain != null && !this.proxyChain.isEmpty() ? this.proxyChain.get(0) : null;
222     }
223 
224     @Override
225     public final TunnelType getTunnelType() {
226         return this.tunnelled;
227     }
228 
229     @Override
230     public final boolean isTunnelled() {
231         return (this.tunnelled == TunnelType.TUNNELLED);
232     }
233 
234     @Override
235     public final LayerType getLayerType() {
236         return this.layered;
237     }
238 
239     @Override
240     public final boolean isLayered() {
241         return (this.layered == LayerType.LAYERED);
242     }
243 
244     @Override
245     public final boolean isSecure() {
246         return this.secure;
247     }
248 
249     /**
250      * Compares this route to another.
251      *
252      * @param obj         the object to compare with
253      *
254      * @return  <code>true</code> if the argument is the same route,
255      *          <code>false</code>
256      */
257     @Override
258     public final boolean equals(final Object obj) {
259         if (this == obj) {
260             return true;
261         }
262         if (obj instanceof HttpRoute) {
263             final HttpRoute that = (HttpRoute) obj;
264             return
265                 // Do the cheapest tests first
266                 (this.secure    == that.secure) &&
267                 (this.tunnelled == that.tunnelled) &&
268                 (this.layered   == that.layered) &&
269                 LangUtils.equals(this.targetHost, that.targetHost) &&
270                 LangUtils.equals(this.localAddress, that.localAddress) &&
271                 LangUtils.equals(this.proxyChain, that.proxyChain);
272         } else {
273             return false;
274         }
275     }
276 
277 
278     /**
279      * Generates a hash code for this route.
280      *
281      * @return  the hash code
282      */
283     @Override
284     public final int hashCode() {
285         int hash = LangUtils.HASH_SEED;
286         hash = LangUtils.hashCode(hash, this.targetHost);
287         hash = LangUtils.hashCode(hash, this.localAddress);
288         if (this.proxyChain != null) {
289             for (final HttpHost element : this.proxyChain) {
290                 hash = LangUtils.hashCode(hash, element);
291             }
292         }
293         hash = LangUtils.hashCode(hash, this.secure);
294         hash = LangUtils.hashCode(hash, this.tunnelled);
295         hash = LangUtils.hashCode(hash, this.layered);
296         return hash;
297     }
298 
299     /**
300      * Obtains a description of this route.
301      *
302      * @return  a human-readable representation of this route
303      */
304     @Override
305     public final String toString() {
306         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
307         if (this.localAddress != null) {
308             cab.append(this.localAddress);
309             cab.append("->");
310         }
311         cab.append('{');
312         if (this.tunnelled == TunnelType.TUNNELLED) {
313             cab.append('t');
314         }
315         if (this.layered == LayerType.LAYERED) {
316             cab.append('l');
317         }
318         if (this.secure) {
319             cab.append('s');
320         }
321         cab.append("}->");
322         if (this.proxyChain != null) {
323             for (final HttpHost aProxyChain : this.proxyChain) {
324                 cab.append(aProxyChain);
325                 cab.append("->");
326             }
327         }
328         cab.append(this.targetHost);
329         return cab.toString();
330     }
331 
332     // default implementation of clone() is sufficient
333     @Override
334     public Object clone() throws CloneNotSupportedException {
335         return super.clone();
336     }
337 
338 }