View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http;
17  
18  import io.netty.handler.codec.AsciiString;
19  
20  import java.util.regex.Matcher;
21  import java.util.regex.Pattern;
22  
23  /**
24   * The version of HTTP or its derived protocols, such as
25   * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
26   * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
27   */
28  public class HttpVersion implements Comparable<HttpVersion> {
29  
30      private static final Pattern VERSION_PATTERN =
31          Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)");
32  
33      private static final String HTTP_1_0_STRING = "HTTP/1.0";
34      private static final String HTTP_1_1_STRING = "HTTP/1.1";
35  
36      /**
37       * HTTP/1.0
38       */
39      public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false);
40  
41      /**
42       * HTTP/1.1
43       */
44      public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true);
45  
46      /**
47       * Returns an existing or new {@link HttpVersion} instance which matches to
48       * the specified protocol version string.  If the specified {@code text} is
49       * equal to {@code "HTTP/1.0"}, {@link #HTTP_1_0} will be returned.  If the
50       * specified {@code text} is equal to {@code "HTTP/1.1"}, {@link #HTTP_1_1}
51       * will be returned.  Otherwise, a new {@link HttpVersion} instance will be
52       * returned.
53       */
54      public static HttpVersion valueOf(String text) {
55          if (text == null) {
56              throw new NullPointerException("text");
57          }
58  
59          text = text.trim();
60  
61          if (text.isEmpty()) {
62              throw new IllegalArgumentException("text is empty");
63          }
64  
65          // Try to match without convert to uppercase first as this is what 99% of all clients
66          // will send anyway. Also there is a change to the RFC to make it clear that it is
67          // expected to be case-sensitive
68          //
69          // See:
70          // * http://trac.tools.ietf.org/wg/httpbis/trac/ticket/1
71          // * http://trac.tools.ietf.org/wg/httpbis/trac/wiki
72          //
73          HttpVersion version = version0(text);
74          if (version == null) {
75              version = new HttpVersion(text, true);
76          }
77          return version;
78      }
79  
80      private static HttpVersion version0(String text) {
81          if (HTTP_1_1_STRING.equals(text)) {
82              return HTTP_1_1;
83          }
84          if (HTTP_1_0_STRING.equals(text)) {
85              return HTTP_1_0;
86          }
87          return null;
88      }
89  
90      private final AsciiString protocolName;
91      private final int majorVersion;
92      private final int minorVersion;
93      private final AsciiString text;
94      private final String textAsString;
95      private final boolean keepAliveDefault;
96  
97      /**
98       * Creates a new HTTP version with the specified version string.  You will
99       * not need to create a new instance unless you are implementing a protocol
100      * derived from HTTP, such as
101      * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
102      * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
103      *
104      * @param keepAliveDefault
105      *        {@code true} if and only if the connection is kept alive unless
106      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
107      */
108     public HttpVersion(String text, boolean keepAliveDefault) {
109         if (text == null) {
110             throw new NullPointerException("text");
111         }
112 
113         text = text.trim().toUpperCase();
114         if (text.isEmpty()) {
115             throw new IllegalArgumentException("empty text");
116         }
117 
118         Matcher m = VERSION_PATTERN.matcher(text);
119         if (!m.matches()) {
120             throw new IllegalArgumentException("invalid version format: " + text);
121         }
122 
123         protocolName = new AsciiString(m.group(1));
124         majorVersion = Integer.parseInt(m.group(2));
125         minorVersion = Integer.parseInt(m.group(3));
126         textAsString = protocolName + "/" + majorVersion + '.' + minorVersion;
127         this.text = new AsciiString(textAsString);
128         this.keepAliveDefault = keepAliveDefault;
129     }
130 
131     /**
132      * Creates a new HTTP version with the specified protocol name and version
133      * numbers.  You will not need to create a new instance unless you are
134      * implementing a protocol derived from HTTP, such as
135      * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
136      * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>
137      *
138      * @param keepAliveDefault
139      *        {@code true} if and only if the connection is kept alive unless
140      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
141      */
142     public HttpVersion(String protocolName, int majorVersion, int minorVersion, boolean keepAliveDefault) {
143         if (protocolName == null) {
144             throw new NullPointerException("protocolName");
145         }
146 
147         protocolName = protocolName.trim().toUpperCase();
148         if (protocolName.isEmpty()) {
149             throw new IllegalArgumentException("empty protocolName");
150         }
151 
152         for (int i = 0; i < protocolName.length(); i ++) {
153             if (Character.isISOControl(protocolName.charAt(i)) ||
154                     Character.isWhitespace(protocolName.charAt(i))) {
155                 throw new IllegalArgumentException("invalid character in protocolName");
156             }
157         }
158 
159         if (majorVersion < 0) {
160             throw new IllegalArgumentException("negative majorVersion");
161         }
162         if (minorVersion < 0) {
163             throw new IllegalArgumentException("negative minorVersion");
164         }
165 
166         this.protocolName = new AsciiString(protocolName);
167         this.majorVersion = majorVersion;
168         this.minorVersion = minorVersion;
169         textAsString = protocolName + '/' + majorVersion + '.' + minorVersion;
170         text = new AsciiString(textAsString);
171         this.keepAliveDefault = keepAliveDefault;
172     }
173 
174     /**
175      * Returns the name of the protocol such as {@code "HTTP"} in {@code "HTTP/1.0"}.
176      */
177     public AsciiString protocolName() {
178         return protocolName;
179     }
180 
181     /**
182      * Returns the name of the protocol such as {@code 1} in {@code "HTTP/1.0"}.
183      */
184     public int majorVersion() {
185         return majorVersion;
186     }
187 
188     /**
189      * Returns the name of the protocol such as {@code 0} in {@code "HTTP/1.0"}.
190      */
191     public int minorVersion() {
192         return minorVersion;
193     }
194 
195     /**
196      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
197      */
198     public AsciiString text() {
199         return text;
200     }
201 
202     /**
203      * Returns {@code true} if and only if the connection is kept alive unless
204      * the {@code "Connection"} header is set to {@code "close"} explicitly.
205      */
206     public boolean isKeepAliveDefault() {
207         return keepAliveDefault;
208     }
209 
210     /**
211      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
212      */
213     @Override
214     public String toString() {
215         return textAsString;
216     }
217 
218     @Override
219     public int hashCode() {
220         return (protocolName().hashCode() * 31 + majorVersion()) * 31 +
221                minorVersion();
222     }
223 
224     @Override
225     public boolean equals(Object o) {
226         if (!(o instanceof HttpVersion)) {
227             return false;
228         }
229 
230         HttpVersion that = (HttpVersion) o;
231         return minorVersion() == that.minorVersion() &&
232                majorVersion() == that.majorVersion() &&
233                protocolName().equals(that.protocolName());
234     }
235 
236     @Override
237     public int compareTo(HttpVersion o) {
238         int v = protocolName().compareTo(o.protocolName());
239         if (v != 0) {
240             return v;
241         }
242 
243         v = majorVersion() - o.majorVersion();
244         if (v != 0) {
245             return v;
246         }
247 
248         return minorVersion() - o.minorVersion();
249     }
250 }