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.core5.http;
29  
30  import java.io.Serializable;
31  import java.net.InetAddress;
32  import java.net.URISyntaxException;
33  import java.util.Locale;
34  
35  import org.apache.hc.core5.annotation.Contract;
36  import org.apache.hc.core5.annotation.ThreadingBehavior;
37  import org.apache.hc.core5.net.NamedEndpoint;
38  import org.apache.hc.core5.net.Ports;
39  import org.apache.hc.core5.net.URIAuthority;
40  import org.apache.hc.core5.util.Args;
41  import org.apache.hc.core5.util.LangUtils;
42  import org.apache.hc.core5.util.TextUtils;
43  
44  /**
45   * Component that holds all details needed to describe an HTTP connection
46   * to a host. This includes remote host name, port and protocol scheme.
47   *
48   * @see org.apache.hc.core5.net.Host
49   *
50   * @since 4.0
51   */
52  @Contract(threading = ThreadingBehavior.IMMUTABLE)
53  public final class HttpHost implements NamedEndpoint, Serializable {
54  
55      private static final long serialVersionUID = -7529410654042457626L;
56  
57      /** The default scheme is "http". */
58      public static final URIScheme DEFAULT_SCHEME = URIScheme.HTTP;
59  
60      /** The host to use. */
61      private final String hostname;
62  
63      /** The lowercase host, for {@link #equals} and {@link #hashCode}. */
64      private final String lcHostname;
65  
66      /** The port to use, defaults to -1 if not set. */
67      private final int port;
68  
69      /** The scheme (lowercased) */
70      private final String schemeName;
71  
72      private final InetAddress address;
73  
74      /**
75       * @throws IllegalArgumentException
76       *             If the port parameter is outside the specified range of valid port values, which is between 0 and
77       *             65535, inclusive. {@code -1} indicates the scheme default port.
78       */
79      private HttpHost(final String hostname, final int port, final String scheme, final boolean internal) {
80          super();
81          this.hostname = hostname;
82          this.lcHostname = hostname;
83          this.schemeName = scheme;
84          this.port = Ports.checkWithDefault(port);
85          this.address = null;
86      }
87  
88      /**
89       * Creates {@code HttpHost} instance with the given scheme, hostname and port.
90       *
91       * @param hostname  the hostname (IP or DNS name)
92       * @param port      the port number.
93       *                  {@code -1} indicates the scheme default port.
94       * @param scheme    the name of the scheme.
95       *                  {@code null} indicates the
96       *                  {@link #DEFAULT_SCHEME default scheme}
97       *
98       * @throws IllegalArgumentException
99       *             If the port parameter is outside the specified range of valid port values, which is between 0 and
100      *             65535, inclusive. {@code -1} indicates the scheme default port.
101      */
102     public HttpHost(final String hostname, final int port, final String scheme) {
103         super();
104         this.hostname   = Args.containsNoBlanks(hostname, "Host name");
105         this.lcHostname = hostname.toLowerCase(Locale.ROOT);
106         if (scheme != null) {
107             this.schemeName = scheme.toLowerCase(Locale.ROOT);
108         } else {
109             this.schemeName = DEFAULT_SCHEME.id;
110         }
111         this.port = Ports.checkWithDefault(port);
112         this.address = null;
113     }
114 
115     /**
116      * Creates {@code HttpHost} instance with the default scheme and the given hostname and port.
117      *
118      * @param hostname  the hostname (IP or DNS name)
119      * @param port      the port number.
120      *                  {@code -1} indicates the scheme default port.
121      *
122      * @throws IllegalArgumentException
123      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
124      *             65535, inclusive. {@code -1} indicates the scheme default port.
125      */
126     public HttpHost(final String hostname, final int port) {
127         this(hostname, port, null);
128     }
129 
130     /**
131      * Creates {@code HttpHost} instance with the given hostname and scheme and the default port for that scheme.
132      *
133      * @param hostname  the hostname (IP or DNS name)
134      * @param scheme    the name of the scheme.
135      *                  {@code null} indicates the
136      *                  {@link #DEFAULT_SCHEME default scheme}
137      *
138      * @throws IllegalArgumentException
139      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
140      *             65535, inclusive. {@code -1} indicates the scheme default port.
141      */
142     public HttpHost(final String hostname, final String scheme) {
143         this(hostname, -1, scheme);
144     }
145 
146     /**
147      * Creates {@code HttpHost} instance from string. Text may not contain any blanks.
148      *
149      * @since 4.4
150      */
151     public static HttpHost create(final String s) throws URISyntaxException {
152         Args.notEmpty(s, "HTTP Host");
153         String text = s;
154         String scheme = null;
155         final int schemeIdx = text.indexOf("://");
156         if (schemeIdx > 0) {
157             scheme = text.substring(0, schemeIdx);
158             if (TextUtils.containsBlanks(scheme)) {
159                 throw new URISyntaxException(s, "scheme contains blanks");
160             }
161             text = text.substring(schemeIdx + 3);
162         }
163         int port = -1;
164         final int portIdx = text.lastIndexOf(":");
165         if (portIdx > 0) {
166             try {
167                 port = Integer.parseInt(text.substring(portIdx + 1));
168             } catch (final NumberFormatException ex) {
169                 throw new URISyntaxException(s, "invalid port");
170             }
171             text = text.substring(0, portIdx);
172         }
173         if (TextUtils.containsBlanks(text)) {
174             throw new URISyntaxException(s, "hostname contains blanks");
175         }
176         return new HttpHost(
177                 text.toLowerCase(Locale.ROOT),
178                 port,
179                 scheme != null ? scheme.toLowerCase(Locale.ROOT) : DEFAULT_SCHEME.id, true);
180     }
181 
182     /**
183      * Creates {@code HttpHost} instance with the default scheme and port and the given hostname.
184      *
185      * @param hostname  the hostname (IP or DNS name)
186      *
187      * @throws IllegalArgumentException
188      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
189      *             65535, inclusive. {@code -1} indicates the scheme default port.
190      */
191     public HttpHost(final String hostname) {
192         this(hostname, -1, null);
193     }
194 
195     /**
196      * Creates {@code HttpHost} instance with the given scheme, inet address and port.
197      *
198      * @param address   the inet address.
199      * @param port      the port number.
200      *                  {@code -1} indicates the scheme default port.
201      * @param scheme    the name of the scheme.
202      *                  {@code null} indicates the
203      *                  {@link #DEFAULT_SCHEME default scheme}
204      *
205      * @throws IllegalArgumentException
206      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
207      *             65535, inclusive. {@code -1} indicates the scheme default port.
208      *
209      * @since 4.3
210      */
211     public HttpHost(final InetAddress address, final int port, final String scheme) {
212         this(Args.notNull(address,"Inet address"), address.getHostName(), port, scheme);
213     }
214     /**
215      * Creates a new {@link HttpHost HttpHost}, specifying all values.
216      * Constructor for HttpHost.
217      *
218      * @param address   the inet address.
219      * @param hostname   the hostname (IP or DNS name)
220      * @param port      the port number.
221      *                  {@code -1} indicates the scheme default port.
222      * @param scheme    the name of the scheme.
223      *                  {@code null} indicates the
224      *                  {@link #DEFAULT_SCHEME default scheme}
225      *
226      * @throws IllegalArgumentException
227      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
228      *             65535, inclusive. {@code -1} indicates the scheme default port.
229      *
230      * @since 4.4
231      */
232     public HttpHost(final InetAddress address, final String hostname, final int port, final String scheme) {
233         super();
234         this.address = Args.notNull(address, "Inet address");
235         this.hostname = Args.notNull(hostname, "Hostname");
236         this.lcHostname = this.hostname.toLowerCase(Locale.ROOT);
237         if (scheme != null) {
238             this.schemeName = scheme.toLowerCase(Locale.ROOT);
239         } else {
240             this.schemeName = DEFAULT_SCHEME.id;
241         }
242         this.port = Ports.checkWithDefault(port);
243     }
244 
245     /**
246      * Creates {@code HttpHost} instance with the default scheme and the given inet address
247      * and port.
248      *
249      * @param address   the inet address.
250      * @param port      the port number.
251      *                  {@code -1} indicates the scheme default port.
252      *
253      * @throws IllegalArgumentException
254      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
255      *             65535, inclusive. {@code -1} indicates the scheme default port.
256      *
257      * @since 4.3
258      */
259     public HttpHost(final InetAddress address, final int port) {
260         this(address, port, null);
261     }
262 
263     /**
264      * Creates {@code HttpHost} instance with the default scheme and port and the given inet
265      * address.
266      *
267      * @param address   the inet address.
268      *
269      * @throws IllegalArgumentException
270      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
271      *             65535, inclusive. {@code -1} indicates the scheme default port.
272      *
273      * @since 4.3
274      */
275     public HttpHost(final InetAddress address) {
276         this(address, -1, null);
277     }
278 
279     /**
280      * @throws IllegalArgumentException
281      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
282      *             65535, inclusive. {@code -1} indicates the scheme default port.
283      *
284      * @since 5.0
285      */
286     public HttpHost(final NamedEndpoint namedEndpoint, final String scheme) {
287         this(namedEndpoint.getHostName(), namedEndpoint.getPort(), scheme);
288     }
289 
290     /**
291      * @throws IllegalArgumentException
292      *             If the port parameter is outside the specified range of valid port values, which is between 0 and
293      *             65535, inclusive. {@code -1} indicates the scheme default port.
294      *
295      * @since 5.0
296      */
297     public HttpHost(final URIAuthority authority) {
298         this(authority, null);
299     }
300 
301     /**
302      * Returns the host name.
303      *
304      * @return the host name (IP or DNS name)
305      */
306     @Override
307     public String getHostName() {
308         return this.hostname;
309     }
310 
311     /**
312      * Returns the port.
313      *
314      * @return the host port, or {@code -1} if not set
315      */
316     @Override
317     public int getPort() {
318         return this.port;
319     }
320 
321     /**
322      * Returns the scheme name.
323      *
324      * @return the scheme name
325      */
326     public String getSchemeName() {
327         return this.schemeName;
328     }
329 
330     /**
331      * Returns the inet address if explicitly set by a constructor,
332      *   {@code null} otherwise.
333      * @return the inet address
334      *
335      * @since 4.3
336      */
337     public InetAddress getAddress() {
338         return this.address;
339     }
340 
341     /**
342      * Return the host URI, as a string.
343      *
344      * @return the host URI
345      */
346     public String toURI() {
347         final StringBuilder buffer = new StringBuilder();
348         buffer.append(this.schemeName);
349         buffer.append("://");
350         buffer.append(this.hostname);
351         if (this.port != -1) {
352             buffer.append(':');
353             buffer.append(Integer.toString(this.port));
354         }
355         return buffer.toString();
356     }
357 
358 
359     /**
360      * Obtains the host string, without scheme prefix.
361      *
362      * @return  the host string, for example {@code localhost:8080}
363      */
364     public String toHostString() {
365         if (this.port != -1) {
366             //the highest port number is 65535, which is length 6 with the addition of the colon
367             final StringBuilder buffer = new StringBuilder(this.hostname.length() + 6);
368             buffer.append(this.hostname);
369             buffer.append(":");
370             buffer.append(Integer.toString(this.port));
371             return buffer.toString();
372         }
373         return this.hostname;
374     }
375 
376 
377     @Override
378     public String toString() {
379         return toURI();
380     }
381 
382 
383     @Override
384     public boolean equals(final Object obj) {
385         if (this == obj) {
386             return true;
387         }
388         if (obj instanceof HttpHost) {
389             final HttpHost that = (HttpHost) obj;
390             return this.lcHostname.equals(that.lcHostname)
391                 && this.port == that.port
392                 && this.schemeName.equals(that.schemeName)
393                 && LangUtils.equals(this.address, that.address);
394         }
395         return false;
396     }
397 
398     /**
399      * @see java.lang.Object#hashCode()
400      */
401     @Override
402     public int hashCode() {
403         int hash = LangUtils.HASH_SEED;
404         hash = LangUtils.hashCode(hash, this.lcHostname);
405         hash = LangUtils.hashCode(hash, this.port);
406         hash = LangUtils.hashCode(hash, this.schemeName);
407         hash = LangUtils.hashCode(hash, address);
408         return hash;
409     }
410 
411 }