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.net.InetSocketAddress;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Objects;
37
38 import org.apache.hc.core5.annotation.Contract;
39 import org.apache.hc.core5.annotation.ThreadingBehavior;
40 import org.apache.hc.core5.http.HttpHost;
41 import org.apache.hc.core5.net.NamedEndpoint;
42 import org.apache.hc.core5.util.Args;
43 import org.apache.hc.core5.util.LangUtils;
44
45 /**
46 * Connection route definition for HTTP requests.
47 *
48 * @since 4.0
49 */
50 @Contract(threading = ThreadingBehavior.IMMUTABLE)
51 public final class HttpRoute implements RouteInfo, Cloneable {
52
53 /** The target host to connect to. */
54 private final HttpHost targetHost;
55
56 /** The target name, if different from the target host, {@code null} otherwise. */
57 private final NamedEndpoint targetName;
58
59 /**
60 * The local address to connect from.
61 * {@code null} indicates that the default should be used.
62 */
63 private final InetAddress localAddress;
64
65 /** The proxy servers, if any. Never null. */
66 private final List<HttpHost> proxyChain;
67
68 /** Whether the the route is tunnelled through the proxy. */
69 private final TunnelType tunnelled;
70
71 /** Whether the route is layered. */
72 private final LayerType layered;
73
74 /** Whether the route is (supposed to be) secure. */
75 private final boolean secure;
76
77 HttpRoute(final HttpHost targetHost,
78 final NamedEndpoint targetName,
79 final InetAddress local,
80 final List<HttpHost> proxies,
81 final boolean secure,
82 final TunnelType tunnelled,
83 final LayerType layered) {
84 Args.notNull(targetHost, "Target host");
85 Args.notNegative(targetHost.getPort(), "Target port");
86 this.targetName = targetName;
87 this.targetHost = targetHost;
88 this.localAddress = local;
89 if (proxies != null && !proxies.isEmpty()) {
90 this.proxyChain = new ArrayList<>(proxies);
91 } else {
92 this.proxyChain = null;
93 }
94 if (tunnelled == TunnelType.TUNNELLED) {
95 Args.check(this.proxyChain != null, "Proxy required if tunnelled");
96 }
97 this.secure = secure;
98 this.tunnelled = tunnelled != null ? tunnelled : TunnelType.PLAIN;
99 this.layered = layered != null ? layered : LayerType.PLAIN;
100 }
101
102 /**
103 * Creates a new route with all attributes specified explicitly.
104 *
105 * @param target the host to which to route
106 * @param local the local address to route from, or
107 * {@code null} for the default
108 * @param proxies the proxy chain to use, or
109 * {@code null} for a direct route
110 * @param secure {@code true} if the route is (to be) secure,
111 * {@code false} otherwise
112 * @param tunnelled the tunnel type of this route
113 * @param layered the layering type of this route
114 */
115 public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies,
116 final boolean secure, final TunnelType tunnelled, final LayerType layered) {
117 this(target, null, local, proxies != null ? Arrays.asList(proxies) : null,
118 secure, tunnelled, layered);
119 }
120
121 /**
122 * Creates a new route with all attributes specified explicitly.
123 *
124 * @param target the host to which to route
125 * @param targetName the target targetName if differs from the target host.
126 * @param local the local address to route from, or
127 * {@code null} for the default
128 * @param proxies the proxy chain to use, or
129 * {@code null} for a direct route
130 * @param secure {@code true} if the route is (to be) secure,
131 * {@code false} otherwise
132 * @param tunnelled the tunnel type of this route
133 * @param layered the layering type of this route
134 *
135 * @since 5.4
136 */
137 public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final InetAddress local, final HttpHost[] proxies,
138 final boolean secure, final TunnelType tunnelled, final LayerType layered) {
139 this(target, targetName, local, proxies != null ? Arrays.asList(proxies) : null,
140 secure, tunnelled, layered);
141 }
142
143 /**
144 * Creates a new route with at most one proxy.
145 *
146 * @param target the host to which to route
147 * @param local the local address to route from, or
148 * {@code null} for the default
149 * @param proxy the proxy to use, or
150 * {@code null} for a direct route
151 * @param secure {@code true} if the route is (to be) secure,
152 * {@code false} otherwise
153 * @param tunnelled {@code true} if the route is (to be) tunnelled
154 * via the proxy,
155 * {@code false} otherwise
156 * @param layered {@code true} if the route includes a
157 * layered protocol,
158 * {@code false} otherwise
159 */
160 public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
161 final boolean secure, final TunnelType tunnelled, final LayerType layered) {
162 this(target, null, local, proxy != null ? Collections.singletonList(proxy) : null,
163 secure, tunnelled, layered);
164 }
165
166 /**
167 * Creates a new direct route.
168 * That is a route without a proxy.
169 *
170 * @param target the host to which to route
171 * @param local the local address to route from, or
172 * {@code null} for the default
173 * @param secure {@code true} if the route is (to be) secure,
174 * {@code false} otherwise
175 */
176 public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) {
177 this(target, null, local, Collections.emptyList(), secure, TunnelType.PLAIN, LayerType.PLAIN);
178 }
179
180 /**
181 * Creates a new direct route. That is a route without a proxy.
182 *
183 * @param target the host to which to route
184 * @param targetName the target targetName if differs from the target host.
185 * @param local the local address to route from, or
186 * {@code null} for the default
187 * @param secure {@code true} if the route is (to be) secure,
188 * {@code false} otherwise
189 *
190 * @since 5.4
191 */
192 public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final InetAddress local, final boolean secure) {
193 this(target, targetName, local, Collections.emptyList(), secure, TunnelType.PLAIN, LayerType.PLAIN);
194 }
195
196 /**
197 * Creates a new direct insecure route.
198 *
199 * @param target the host to which to route
200 */
201 public HttpRoute(final HttpHost target) {
202 this(target, null, null, Collections.emptyList(), false, TunnelType.PLAIN, LayerType.PLAIN);
203 }
204
205 /**
206 * Creates a new route through a proxy.
207 * When using this constructor, the {@code proxy} MUST be given.
208 * For convenience, it is assumed that a secure connection will be
209 * layered over a tunnel through the proxy.
210 *
211 * @param target the host to which to route
212 * @param targetName the target targetName if differs from the target host.
213 * @param local the local address to route from, or
214 * {@code null} for the default
215 * @param proxy the proxy to use
216 * @param secure {@code true} if the route is (to be) secure,
217 * {@code false} otherwise
218 *
219 * @since 5.4
220 */
221 public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final InetAddress local,
222 final HttpHost proxy, final boolean secure) {
223 this(target, targetName, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
224 secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
225 secure ? LayerType.LAYERED : LayerType.PLAIN);
226 }
227
228 /**
229 * Creates a new route through a proxy.
230 * When using this constructor, the {@code proxy} MUST be given.
231 * For convenience, it is assumed that a secure connection will be
232 * layered over a tunnel through the proxy.
233 *
234 * @param target the host to which to route
235 * @param local the local address to route from, or
236 * {@code null} for the default
237 * @param proxy the proxy to use
238 * @param secure {@code true} if the route is (to be) secure,
239 * {@code false} otherwise
240 */
241 public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
242 final boolean secure) {
243 this(target, null, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
244 secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
245 secure ? LayerType.LAYERED : LayerType.PLAIN);
246 }
247
248 /**
249 * Creates a new plain route through a proxy.
250 *
251 * @param target the host to which to route
252 * @param proxy the proxy to use
253 *
254 * @since 4.3
255 */
256 public HttpRoute(final HttpHost target, final HttpHost proxy) {
257 this(target, null, proxy, false);
258 }
259
260 @Override
261 public HttpHost getTargetHost() {
262 return this.targetHost;
263 }
264
265 /**
266 * @since 5.4
267 */
268 @Override
269 public NamedEndpoint getTargetName() {
270 return targetName;
271 }
272
273 @Override
274 public InetAddress getLocalAddress() {
275 return this.localAddress;
276 }
277
278 public InetSocketAddress getLocalSocketAddress() {
279 return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
280 }
281
282 @Override
283 public int getHopCount() {
284 return proxyChain != null ? proxyChain.size() + 1 : 1;
285 }
286
287 @Override
288 public HttpHost getHopTarget(final int hop) {
289 Args.notNegative(hop, "Hop index");
290 final int hopcount = getHopCount();
291 Args.check(hop < hopcount, "Hop index exceeds tracked route length");
292 if (hop < hopcount - 1) {
293 return this.proxyChain.get(hop);
294 }
295 return this.targetHost;
296 }
297
298 @Override
299 public HttpHost getProxyHost() {
300 return proxyChain != null && !this.proxyChain.isEmpty() ? this.proxyChain.get(0) : null;
301 }
302
303 @Override
304 public TunnelType getTunnelType() {
305 return this.tunnelled;
306 }
307
308 @Override
309 public boolean isTunnelled() {
310 return this.tunnelled == TunnelType.TUNNELLED;
311 }
312
313 @Override
314 public LayerType getLayerType() {
315 return this.layered;
316 }
317
318 @Override
319 public boolean isLayered() {
320 return this.layered == LayerType.LAYERED;
321 }
322
323 @Override
324 public boolean isSecure() {
325 return this.secure;
326 }
327
328 /**
329 * Compares this route to another.
330 *
331 * @param obj the object to compare with
332 *
333 * @return {@code true} if the argument is the same route,
334 * {@code false}
335 */
336 @Override
337 public boolean equals(final Object obj) {
338 if (this == obj) {
339 return true;
340 }
341 if (obj instanceof HttpRoute) {
342 final HttpRoute that = (HttpRoute) obj;
343 return
344 // Do the cheapest tests first
345 this.secure == that.secure &&
346 this.tunnelled == that.tunnelled &&
347 this.layered == that.layered &&
348 Objects.equals(this.targetHost, that.targetHost) &&
349 Objects.equals(this.targetName, that.targetName) &&
350 Objects.equals(this.localAddress, that.localAddress) &&
351 Objects.equals(this.proxyChain, that.proxyChain);
352 }
353 return false;
354 }
355
356
357 /**
358 * Generates a hash code for this route.
359 *
360 * @return the hash code
361 */
362 @Override
363 public int hashCode() {
364 int hash = LangUtils.HASH_SEED;
365 hash = LangUtils.hashCode(hash, this.targetHost);
366 hash = LangUtils.hashCode(hash, this.targetName);
367 hash = LangUtils.hashCode(hash, this.localAddress);
368 if (this.proxyChain != null) {
369 for (final HttpHost element : this.proxyChain) {
370 hash = LangUtils.hashCode(hash, element);
371 }
372 }
373 hash = LangUtils.hashCode(hash, this.secure);
374 hash = LangUtils.hashCode(hash, this.tunnelled);
375 hash = LangUtils.hashCode(hash, this.layered);
376 return hash;
377 }
378
379 /**
380 * Obtains a description of this route.
381 *
382 * @return a human-readable representation of this route
383 */
384 @Override
385 public String toString() {
386 final StringBuilder cab = new StringBuilder(50 + getHopCount() * 30);
387 if (this.localAddress != null) {
388 cab.append(this.localAddress);
389 cab.append("->");
390 }
391 cab.append('{');
392 if (this.tunnelled == TunnelType.TUNNELLED) {
393 cab.append('t');
394 }
395 if (this.layered == LayerType.LAYERED) {
396 cab.append('l');
397 }
398 if (this.secure) {
399 cab.append('s');
400 }
401 cab.append("}->");
402 if (this.proxyChain != null) {
403 for (final HttpHost aProxyChain : this.proxyChain) {
404 cab.append(aProxyChain);
405 cab.append("->");
406 }
407 }
408 if (this.targetName != null) {
409 cab.append(this.targetName);
410 cab.append("/");
411 }
412 cab.append("[");
413 cab.append(this.targetHost);
414 cab.append("]");
415 return cab.toString();
416 }
417
418 // default implementation of clone() is sufficient
419 @Override
420 public Object clone() throws CloneNotSupportedException {
421 return super.clone();
422 }
423
424 }