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