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.hc.client5.http;
29  
30  import java.net.InetAddress;
31  
32  import org.apache.hc.core5.http.HttpHost;
33  import org.apache.hc.core5.util.Args;
34  import org.apache.hc.core5.util.Asserts;
35  import org.apache.hc.core5.util.LangUtils;
36  
37  /**
38   * Helps tracking the steps in establishing a route.
39   *
40   * @since 4.0
41   */
42  public final class RouteTracker implements RouteInfo, Cloneable {
43  
44      /** The target host to connect to. */
45      private final HttpHost targetHost;
46  
47      /**
48       * The local address to connect from.
49       * {@code null} indicates that the default should be used.
50       */
51      private final InetAddress localAddress;
52  
53      // the attributes above are fixed at construction time
54      // now follow attributes that indicate the established route
55  
56      /** Whether the first hop of the route is established. */
57      private boolean connected;
58  
59      /** The proxy chain, if any. */
60      private HttpHost[] proxyChain;
61  
62      /** Whether the the route is tunnelled end-to-end through proxies. */
63      private TunnelType tunnelled;
64  
65      /** Whether the route is layered over a tunnel. */
66      private LayerType layered;
67  
68      /** Whether the route is secure. */
69      private boolean secure;
70  
71      /**
72       * Creates a new route tracker.
73       * The target and origin need to be specified at creation time.
74       *
75       * @param target    the host to which to route
76       * @param local     the local address to route from, or
77       *                  {@code null} for the default
78       */
79      public RouteTracker(final HttpHost target, final InetAddress local) {
80          Args.notNull(target, "Target host");
81          this.targetHost   = target;
82          this.localAddress = local;
83          this.tunnelled    = TunnelType.PLAIN;
84          this.layered      = LayerType.PLAIN;
85      }
86  
87      /**
88       * @since 4.2
89       */
90      public void reset() {
91          this.connected = false;
92          this.proxyChain = null;
93          this.tunnelled = TunnelType.PLAIN;
94          this.layered = LayerType.PLAIN;
95          this.secure = false;
96      }
97  
98      /**
99       * Creates a new tracker for the given route.
100      * Only target and origin are taken from the route,
101      * everything else remains to be tracked.
102      *
103      * @param route     the route to track
104      */
105     public RouteTracker(final HttpRoute route) {
106         this(route.getTargetHost(), route.getLocalAddress());
107     }
108 
109     /**
110      * Tracks connecting to the target.
111      *
112      * @param secure    {@code true} if the route is secure,
113      *                  {@code false} otherwise
114      */
115     public void connectTarget(final boolean secure) {
116         Asserts.check(!this.connected, "Already connected");
117         this.connected = true;
118         this.secure = secure;
119     }
120 
121     /**
122      * Tracks connecting to the first proxy.
123      *
124      * @param proxy     the proxy connected to
125      * @param secure    {@code true} if the route is secure,
126      *                  {@code false} otherwise
127      */
128     public void connectProxy(final HttpHost proxy, final boolean secure) {
129         Args.notNull(proxy, "Proxy host");
130         Asserts.check(!this.connected, "Already connected");
131         this.connected  = true;
132         this.proxyChain = new HttpHost[]{ proxy };
133         this.secure     = secure;
134     }
135 
136     /**
137      * Tracks tunnelling to the target.
138      *
139      * @param secure    {@code true} if the route is secure,
140      *                  {@code false} otherwise
141      */
142     public void tunnelTarget(final boolean secure) {
143         Asserts.check(this.connected, "No tunnel unless connected");
144         Asserts.notNull(this.proxyChain, "No tunnel without proxy");
145         this.tunnelled = TunnelType.TUNNELLED;
146         this.secure    = secure;
147     }
148 
149     /**
150      * Tracks tunnelling to a proxy in a proxy chain.
151      * This will extend the tracked proxy chain, but it does not mark
152      * the route as tunnelled. Only end-to-end tunnels are considered there.
153      *
154      * @param proxy     the proxy tunnelled to
155      * @param secure    {@code true} if the route is secure,
156      *                  {@code false} otherwise
157      */
158     public void tunnelProxy(final HttpHost proxy, final boolean secure) {
159         Args.notNull(proxy, "Proxy host");
160         Asserts.check(this.connected, "No tunnel unless connected");
161         Asserts.notNull(this.proxyChain, "No tunnel without proxy");
162         // prepare an extended proxy chain
163         final HttpHost[] proxies = new HttpHost[this.proxyChain.length+1];
164         System.arraycopy(this.proxyChain, 0,
165                          proxies, 0, this.proxyChain.length);
166         proxies[proxies.length-1] = proxy;
167 
168         this.proxyChain = proxies;
169         this.secure     = secure;
170     }
171 
172     /**
173      * Tracks layering a protocol.
174      *
175      * @param secure    {@code true} if the route is secure,
176      *                  {@code false} otherwise
177      */
178     public void layerProtocol(final boolean secure) {
179         // it is possible to layer a protocol over a direct connection,
180         // although this case is probably not considered elsewhere
181         Asserts.check(this.connected, "No layered protocol unless connected");
182         this.layered = LayerType.LAYERED;
183         this.secure  = secure;
184     }
185 
186     @Override
187     public HttpHost getTargetHost() {
188         return this.targetHost;
189     }
190 
191     @Override
192     public InetAddress getLocalAddress() {
193         return this.localAddress;
194     }
195 
196     @Override
197     public int getHopCount() {
198         int hops = 0;
199         if (this.connected) {
200             if (proxyChain == null) {
201                 hops = 1;
202             } else {
203                 hops = proxyChain.length + 1;
204             }
205         }
206         return hops;
207     }
208 
209     @Override
210     public HttpHost getHopTarget(final int hop) {
211         Args.notNegative(hop, "Hop index");
212         final int hopcount = getHopCount();
213         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
214         HttpHost result = null;
215         if (hop < hopcount-1) {
216             result = this.proxyChain[hop];
217         } else {
218             result = this.targetHost;
219         }
220 
221         return result;
222     }
223 
224     @Override
225     public HttpHost getProxyHost() {
226         return (this.proxyChain == null) ? null : this.proxyChain[0];
227     }
228 
229     public boolean isConnected() {
230         return this.connected;
231     }
232 
233     @Override
234     public TunnelType getTunnelType() {
235         return this.tunnelled;
236     }
237 
238     @Override
239     public boolean isTunnelled() {
240         return (this.tunnelled == TunnelType.TUNNELLED);
241     }
242 
243     @Override
244     public LayerType getLayerType() {
245         return this.layered;
246     }
247 
248     @Override
249     public boolean isLayered() {
250         return (this.layered == LayerType.LAYERED);
251     }
252 
253     @Override
254     public boolean isSecure() {
255         return this.secure;
256     }
257 
258     /**
259      * Obtains the tracked route.
260      * If a route has been tracked, it is {@link #isConnected connected}.
261      * If not connected, nothing has been tracked so far.
262      *
263      * @return  the tracked route, or
264      *          {@code null} if nothing has been tracked so far
265      */
266     public HttpRoute toRoute() {
267         return !this.connected ?
268             null : new HttpRoute(this.targetHost, this.localAddress,
269                                  this.proxyChain, this.secure,
270                                  this.tunnelled, this.layered);
271     }
272 
273     /**
274      * Compares this tracked route to another.
275      *
276      * @param o         the object to compare with
277      *
278      * @return  {@code true} if the argument is the same tracked route,
279      *          {@code false}
280      */
281     @Override
282     public boolean equals(final Object o) {
283         if (o == this) {
284             return true;
285         }
286         if (!(o instanceof RouteTracker)) {
287             return false;
288         }
289 
290         final RouteTracker./../../../org/apache/hc/client5/http/RouteTracker.html#RouteTracker">RouteTracker that = (RouteTracker) o;
291         return
292             // Do the cheapest checks first
293             (this.connected == that.connected) &&
294             (this.secure    == that.secure) &&
295             (this.tunnelled == that.tunnelled) &&
296             (this.layered   == that.layered) &&
297             LangUtils.equals(this.targetHost, that.targetHost) &&
298             LangUtils.equals(this.localAddress, that.localAddress) &&
299             LangUtils.equals(this.proxyChain, that.proxyChain);
300     }
301 
302     /**
303      * Generates a hash code for this tracked route.
304      * Route trackers are modifiable and should therefore not be used
305      * as lookup keys. Use {@link #toRoute toRoute} to obtain an
306      * unmodifiable representation of the tracked route.
307      *
308      * @return  the hash code
309      */
310     @Override
311     public int hashCode() {
312         int hash = LangUtils.HASH_SEED;
313         hash = LangUtils.hashCode(hash, this.targetHost);
314         hash = LangUtils.hashCode(hash, this.localAddress);
315         if (this.proxyChain != null) {
316             for (final HttpHost element : this.proxyChain) {
317                 hash = LangUtils.hashCode(hash, element);
318             }
319         }
320         hash = LangUtils.hashCode(hash, this.connected);
321         hash = LangUtils.hashCode(hash, this.secure);
322         hash = LangUtils.hashCode(hash, this.tunnelled);
323         hash = LangUtils.hashCode(hash, this.layered);
324         return hash;
325     }
326 
327     /**
328      * Obtains a description of the tracked route.
329      *
330      * @return  a human-readable representation of the tracked route
331      */
332     @Override
333     public String toString() {
334         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
335 
336         cab.append("RouteTracker[");
337         if (this.localAddress != null) {
338             cab.append(this.localAddress);
339             cab.append("->");
340         }
341         cab.append('{');
342         if (this.connected) {
343             cab.append('c');
344         }
345         if (this.tunnelled == TunnelType.TUNNELLED) {
346             cab.append('t');
347         }
348         if (this.layered == LayerType.LAYERED) {
349             cab.append('l');
350         }
351         if (this.secure) {
352             cab.append('s');
353         }
354         cab.append("}->");
355         if (this.proxyChain != null) {
356             for (final HttpHost element : this.proxyChain) {
357                 cab.append(element);
358                 cab.append("->");
359             }
360         }
361         cab.append(this.targetHost);
362         cab.append(']');
363 
364         return cab.toString();
365     }
366 
367 
368     // default implementation of clone() is sufficient
369     @Override
370     public Object clone() throws CloneNotSupportedException {
371         return super.clone();
372     }
373 
374 }