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