001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.conn.routing;
029
030import java.net.InetAddress;
031import java.net.InetSocketAddress;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Collections;
035import java.util.List;
036
037import org.apache.http.HttpHost;
038import org.apache.http.annotation.Contract;
039import org.apache.http.annotation.ThreadingBehavior;
040import org.apache.http.util.Args;
041import org.apache.http.util.LangUtils;
042
043/**
044 * The route for a request.
045 *
046 * @since 4.0
047 */
048@Contract(threading = ThreadingBehavior.IMMUTABLE)
049public final class HttpRoute implements RouteInfo, Cloneable {
050
051    /** The target host to connect to. */
052    private final HttpHost targetHost;
053
054    /**
055     * The local address to connect from.
056     * {@code null} indicates that the default should be used.
057     */
058    private final InetAddress localAddress;
059
060    /** The proxy servers, if any. Never null. */
061    private final List<HttpHost> proxyChain;
062
063    /** Whether the the route is tunnelled through the proxy. */
064    private final TunnelType tunnelled;
065
066    /** Whether the route is layered. */
067    private final LayerType layered;
068
069    /** Whether the route is (supposed to be) secure. */
070    private final boolean secure;
071
072    private HttpRoute(final HttpHost target, final InetAddress local, final List<HttpHost> proxies,
073                     final boolean secure, final TunnelType tunnelled, final LayerType layered) {
074        Args.notNull(target, "Target host");
075        this.targetHost = normalize(target);
076        this.localAddress = local;
077        if (proxies != null && !proxies.isEmpty()) {
078            this.proxyChain = new ArrayList<HttpHost>(proxies);
079        } else {
080            this.proxyChain = null;
081        }
082        if (tunnelled == TunnelType.TUNNELLED) {
083            Args.check(this.proxyChain != null, "Proxy required if tunnelled");
084        }
085        this.secure       = secure;
086        this.tunnelled    = tunnelled != null ? tunnelled : TunnelType.PLAIN;
087        this.layered      = layered != null ? layered : LayerType.PLAIN;
088    }
089
090    //TODO: to be removed in 5.0
091    private static int getDefaultPort(final String schemeName) {
092        if ("http".equalsIgnoreCase(schemeName)) {
093            return 80;
094        } else if ("https".equalsIgnoreCase(schemeName)) {
095            return 443;
096        } else {
097            return -1;
098        }
099
100    }
101
102    //TODO: to be removed in 5.0
103    private static HttpHost normalize(final HttpHost target) {
104        if (target.getPort() >= 0 ) {
105            return target;
106        } else {
107            final InetAddress address = target.getAddress();
108            final String schemeName = target.getSchemeName();
109            if (address != null) {
110                return new HttpHost(address, getDefaultPort(schemeName), schemeName);
111            } else {
112                final String hostName = target.getHostName();
113                return new HttpHost(hostName, getDefaultPort(schemeName), schemeName);
114            }
115        }
116    }
117
118    /**
119     * Creates a new route with all attributes specified explicitly.
120     *
121     * @param target    the host to which to route
122     * @param local     the local address to route from, or
123     *                  {@code null} for the default
124     * @param proxies   the proxy chain to use, or
125     *                  {@code null} for a direct route
126     * @param secure    {@code true} if the route is (to be) secure,
127     *                  {@code false} otherwise
128     * @param tunnelled the tunnel type of this route
129     * @param layered   the layering type of this route
130     */
131    public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies,
132                     final boolean secure, final TunnelType tunnelled, final LayerType layered) {
133        this(target, local, proxies != null ? Arrays.asList(proxies) : null,
134                secure, tunnelled, layered);
135    }
136
137    /**
138     * Creates a new route with at most one proxy.
139     *
140     * @param target    the host to which to route
141     * @param local     the local address to route from, or
142     *                  {@code null} for the default
143     * @param proxy     the proxy to use, or
144     *                  {@code null} for a direct route
145     * @param secure    {@code true} if the route is (to be) secure,
146     *                  {@code false} otherwise
147     * @param tunnelled {@code true} if the route is (to be) tunnelled
148     *                  via the proxy,
149     *                  {@code false} otherwise
150     * @param layered   {@code true} if the route includes a
151     *                  layered protocol,
152     *                  {@code false} otherwise
153     */
154    public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
155                     final boolean secure, final TunnelType tunnelled, final LayerType layered) {
156        this(target, local, proxy != null ? Collections.singletonList(proxy) : null,
157                secure, tunnelled, layered);
158    }
159
160    /**
161     * Creates a new direct route.
162     * That is a route without a proxy.
163     *
164     * @param target    the host to which to route
165     * @param local     the local address to route from, or
166     *                  {@code null} for the default
167     * @param secure    {@code true} if the route is (to be) secure,
168     *                  {@code false} otherwise
169     */
170    public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) {
171        this(target, local, Collections.<HttpHost>emptyList(), secure,
172                TunnelType.PLAIN, LayerType.PLAIN);
173    }
174
175    /**
176     * Creates a new direct insecure route.
177     *
178     * @param target    the host to which to route
179     */
180    public HttpRoute(final HttpHost target) {
181        this(target, null, Collections.<HttpHost>emptyList(), false,
182                TunnelType.PLAIN, LayerType.PLAIN);
183    }
184
185    /**
186     * Creates a new route through a proxy.
187     * When using this constructor, the {@code proxy} MUST be given.
188     * For convenience, it is assumed that a secure connection will be
189     * layered over a tunnel through the proxy.
190     *
191     * @param target    the host to which to route
192     * @param local     the local address to route from, or
193     *                  {@code null} for the default
194     * @param proxy     the proxy to use
195     * @param secure    {@code true} if the route is (to be) secure,
196     *                  {@code false} otherwise
197     */
198    public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
199                     final boolean secure) {
200        this(target, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
201             secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
202             secure ? LayerType.LAYERED    : LayerType.PLAIN);
203    }
204
205    /**
206     * Creates a new plain route through a proxy.
207     *
208     * @param target    the host to which to route
209     * @param proxy     the proxy to use
210     *
211     * @since 4.3
212     */
213    public HttpRoute(final HttpHost target, final HttpHost proxy) {
214        this(target, null, proxy, false);
215    }
216
217    @Override
218    public final HttpHost getTargetHost() {
219        return this.targetHost;
220    }
221
222    @Override
223    public final InetAddress getLocalAddress() {
224        return this.localAddress;
225    }
226
227    public final InetSocketAddress getLocalSocketAddress() {
228        return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
229    }
230
231    @Override
232    public final int getHopCount() {
233        return proxyChain != null ? proxyChain.size() + 1 : 1;
234    }
235
236    @Override
237    public final HttpHost getHopTarget(final int hop) {
238        Args.notNegative(hop, "Hop index");
239        final int hopcount = getHopCount();
240        Args.check(hop < hopcount, "Hop index exceeds tracked route length");
241        if (hop < hopcount - 1) {
242            return this.proxyChain.get(hop);
243        } else {
244            return this.targetHost;
245        }
246    }
247
248    @Override
249    public final HttpHost getProxyHost() {
250        return proxyChain != null && !this.proxyChain.isEmpty() ? this.proxyChain.get(0) : null;
251    }
252
253    @Override
254    public final TunnelType getTunnelType() {
255        return this.tunnelled;
256    }
257
258    @Override
259    public final boolean isTunnelled() {
260        return (this.tunnelled == TunnelType.TUNNELLED);
261    }
262
263    @Override
264    public final LayerType getLayerType() {
265        return this.layered;
266    }
267
268    @Override
269    public final boolean isLayered() {
270        return (this.layered == LayerType.LAYERED);
271    }
272
273    @Override
274    public final boolean isSecure() {
275        return this.secure;
276    }
277
278    /**
279     * Compares this route to another.
280     *
281     * @param obj         the object to compare with
282     *
283     * @return  {@code true} if the argument is the same route,
284     *          {@code false}
285     */
286    @Override
287    public final boolean equals(final Object obj) {
288        if (this == obj) {
289            return true;
290        }
291        if (obj instanceof HttpRoute) {
292            final HttpRoute that = (HttpRoute) obj;
293            return
294                // Do the cheapest tests first
295                (this.secure    == that.secure) &&
296                (this.tunnelled == that.tunnelled) &&
297                (this.layered   == that.layered) &&
298                LangUtils.equals(this.targetHost, that.targetHost) &&
299                LangUtils.equals(this.localAddress, that.localAddress) &&
300                LangUtils.equals(this.proxyChain, that.proxyChain);
301        } else {
302            return false;
303        }
304    }
305
306
307    /**
308     * Generates a hash code for this route.
309     *
310     * @return  the hash code
311     */
312    @Override
313    public final int hashCode() {
314        int hash = LangUtils.HASH_SEED;
315        hash = LangUtils.hashCode(hash, this.targetHost);
316        hash = LangUtils.hashCode(hash, this.localAddress);
317        if (this.proxyChain != null) {
318            for (final HttpHost element : this.proxyChain) {
319                hash = LangUtils.hashCode(hash, element);
320            }
321        }
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 this route.
330     *
331     * @return  a human-readable representation of this route
332     */
333    @Override
334    public final String toString() {
335        final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
336        if (this.localAddress != null) {
337            cab.append(this.localAddress);
338            cab.append("->");
339        }
340        cab.append('{');
341        if (this.tunnelled == TunnelType.TUNNELLED) {
342            cab.append('t');
343        }
344        if (this.layered == LayerType.LAYERED) {
345            cab.append('l');
346        }
347        if (this.secure) {
348            cab.append('s');
349        }
350        cab.append("}->");
351        if (this.proxyChain != null) {
352            for (final HttpHost aProxyChain : this.proxyChain) {
353                cab.append(aProxyChain);
354                cab.append("->");
355            }
356        }
357        cab.append(this.targetHost);
358        return cab.toString();
359    }
360
361    // default implementation of clone() is sufficient
362    @Override
363    public Object clone() throws CloneNotSupportedException {
364        return super.clone();
365    }
366
367}