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 static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
19  
20  import io.netty.buffer.ByteBuf;
21  import io.netty.util.CharsetUtil;
22  import io.netty.util.internal.ObjectUtil;
23  
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  /**
28   * The version of HTTP or its derived protocols, such as
29   * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
30   * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
31   */
32  public class HttpVersion implements Comparable<HttpVersion> {
33  
34      private static final Pattern VERSION_PATTERN =
35          Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)");
36  
37      private static final String HTTP_1_0_STRING = "HTTP/1.0";
38      private static final String HTTP_1_1_STRING = "HTTP/1.1";
39  
40      /**
41       * HTTP/1.0
42       */
43      public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false, true);
44  
45      /**
46       * HTTP/1.1
47       */
48      public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true, true);
49  
50      /**
51       * Returns an existing or new {@link HttpVersion} instance which matches to
52       * the specified protocol version string.  If the specified {@code text} is
53       * equal to {@code "HTTP/1.0"}, {@link #HTTP_1_0} will be returned.  If the
54       * specified {@code text} is equal to {@code "HTTP/1.1"}, {@link #HTTP_1_1}
55       * will be returned.  Otherwise, a new {@link HttpVersion} instance will be
56       * returned.
57       */
58      public static HttpVersion valueOf(String text) {
59          ObjectUtil.checkNotNull(text, "text");
60  
61          text = text.trim();
62  
63          if (text.isEmpty()) {
64              throw new IllegalArgumentException("text is empty (possibly HTTP/0.9)");
65          }
66  
67          // Try to match without convert to uppercase first as this is what 99% of all clients
68          // will send anyway. Also there is a change to the RFC to make it clear that it is
69          // expected to be case-sensitive
70          //
71          // See:
72          // * http://trac.tools.ietf.org/wg/httpbis/trac/ticket/1
73          // * http://trac.tools.ietf.org/wg/httpbis/trac/wiki
74          //
75          HttpVersion version = version0(text);
76          if (version == null) {
77              version = new HttpVersion(text, true);
78          }
79          return version;
80      }
81  
82      private static HttpVersion version0(String text) {
83          if (HTTP_1_1_STRING.equals(text)) {
84              return HTTP_1_1;
85          }
86          if (HTTP_1_0_STRING.equals(text)) {
87              return HTTP_1_0;
88          }
89          return null;
90      }
91  
92      private final String protocolName;
93      private final int majorVersion;
94      private final int minorVersion;
95      private final String text;
96      private final boolean keepAliveDefault;
97      private final byte[] bytes;
98  
99      /**
100      * Creates a new HTTP version with the specified version string.  You will
101      * not need to create a new instance unless you are implementing a protocol
102      * derived from HTTP, such as
103      * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
104      * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
105      *
106      * @param keepAliveDefault
107      *        {@code true} if and only if the connection is kept alive unless
108      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
109      */
110     public HttpVersion(String text, boolean keepAliveDefault) {
111         ObjectUtil.checkNotNull(text, "text");
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 = m.group(1);
124         majorVersion = Integer.parseInt(m.group(2));
125         minorVersion = Integer.parseInt(m.group(3));
126         this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
127         this.keepAliveDefault = keepAliveDefault;
128         bytes = null;
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(
143             String protocolName, int majorVersion, int minorVersion,
144             boolean keepAliveDefault) {
145         this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
146     }
147 
148     private HttpVersion(
149             String protocolName, int majorVersion, int minorVersion,
150             boolean keepAliveDefault, boolean bytes) {
151         ObjectUtil.checkNotNull(protocolName, "protocolName");
152 
153         protocolName = protocolName.trim().toUpperCase();
154         if (protocolName.isEmpty()) {
155             throw new IllegalArgumentException("empty protocolName");
156         }
157 
158         for (int i = 0; i < protocolName.length(); i ++) {
159             if (Character.isISOControl(protocolName.charAt(i)) ||
160                     Character.isWhitespace(protocolName.charAt(i))) {
161                 throw new IllegalArgumentException("invalid character in protocolName");
162             }
163         }
164 
165         checkPositiveOrZero(majorVersion, "majorVersion");
166         checkPositiveOrZero(minorVersion, "minorVersion");
167 
168         this.protocolName = protocolName;
169         this.majorVersion = majorVersion;
170         this.minorVersion = minorVersion;
171         text = protocolName + '/' + majorVersion + '.' + minorVersion;
172         this.keepAliveDefault = keepAliveDefault;
173 
174         if (bytes) {
175             this.bytes = text.getBytes(CharsetUtil.US_ASCII);
176         } else {
177             this.bytes = null;
178         }
179     }
180 
181     /**
182      * Returns the name of the protocol such as {@code "HTTP"} in {@code "HTTP/1.0"}.
183      */
184     public String protocolName() {
185         return protocolName;
186     }
187 
188     /**
189      * Returns the name of the protocol such as {@code 1} in {@code "HTTP/1.0"}.
190      */
191     public int majorVersion() {
192         return majorVersion;
193     }
194 
195     /**
196      * Returns the name of the protocol such as {@code 0} in {@code "HTTP/1.0"}.
197      */
198     public int minorVersion() {
199         return minorVersion;
200     }
201 
202     /**
203      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
204      */
205     public String text() {
206         return text;
207     }
208 
209     /**
210      * Returns {@code true} if and only if the connection is kept alive unless
211      * the {@code "Connection"} header is set to {@code "close"} explicitly.
212      */
213     public boolean isKeepAliveDefault() {
214         return keepAliveDefault;
215     }
216 
217     /**
218      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
219      */
220     @Override
221     public String toString() {
222         return text();
223     }
224 
225     @Override
226     public int hashCode() {
227         return (protocolName().hashCode() * 31 + majorVersion()) * 31 +
228                minorVersion();
229     }
230 
231     @Override
232     public boolean equals(Object o) {
233         if (!(o instanceof HttpVersion)) {
234             return false;
235         }
236 
237         HttpVersion that = (HttpVersion) o;
238         return minorVersion() == that.minorVersion() &&
239                majorVersion() == that.majorVersion() &&
240                protocolName().equals(that.protocolName());
241     }
242 
243     @Override
244     public int compareTo(HttpVersion o) {
245         int v = protocolName().compareTo(o.protocolName());
246         if (v != 0) {
247             return v;
248         }
249 
250         v = majorVersion() - o.majorVersion();
251         if (v != 0) {
252             return v;
253         }
254 
255         return minorVersion() - o.minorVersion();
256     }
257 
258     void encode(ByteBuf buf) {
259         if (bytes == null) {
260             buf.writeCharSequence(text, CharsetUtil.US_ASCII);
261         } else {
262             buf.writeBytes(bytes);
263         }
264     }
265 }