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