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          // TODO: Remove the uppercase conversion in 4.1.0 as the RFC state it must be HTTP (uppercase)
75          //       See https://github.com/netty/netty/issues/1682
76  
77          HttpVersion version = version0(text);
78          if (version == null) {
79              text = text.toUpperCase();
80              // try again after convert to uppercase
81              version = version0(text);
82              if (version == null) {
83                  // still no match, construct a new one
84                  version = new HttpVersion(text, true);
85              }
86          }
87          return version;
88      }
89  
90      private static HttpVersion version0(String text) {
91          if (HTTP_1_1_STRING.equals(text)) {
92              return HTTP_1_1;
93          }
94          if (HTTP_1_0_STRING.equals(text)) {
95              return HTTP_1_0;
96          }
97          return null;
98      }
99  
100     private final String protocolName;
101     private final int majorVersion;
102     private final int minorVersion;
103     private final String text;
104     private final boolean keepAliveDefault;
105     private final byte[] bytes;
106 
107     /**
108      * Creates a new HTTP version with the specified version string.  You will
109      * not need to create a new instance unless you are implementing a protocol
110      * derived from HTTP, such as
111      * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
112      * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
113      *
114      * @param keepAliveDefault
115      *        {@code true} if and only if the connection is kept alive unless
116      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
117      */
118     public HttpVersion(String text, boolean keepAliveDefault) {
119         if (text == null) {
120             throw new NullPointerException("text");
121         }
122 
123         text = text.trim().toUpperCase();
124         if (text.isEmpty()) {
125             throw new IllegalArgumentException("empty text");
126         }
127 
128         Matcher m = VERSION_PATTERN.matcher(text);
129         if (!m.matches()) {
130             throw new IllegalArgumentException("invalid version format: " + text);
131         }
132 
133         protocolName = m.group(1);
134         majorVersion = Integer.parseInt(m.group(2));
135         minorVersion = Integer.parseInt(m.group(3));
136         this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
137         this.keepAliveDefault = keepAliveDefault;
138         bytes = null;
139     }
140 
141     /**
142      * Creates a new HTTP version with the specified protocol name and version
143      * numbers.  You will not need to create a new instance unless you are
144      * implementing a protocol derived from HTTP, such as
145      * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
146      * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>
147      *
148      * @param keepAliveDefault
149      *        {@code true} if and only if the connection is kept alive unless
150      *        the {@code "Connection"} header is set to {@code "close"} explicitly.
151      */
152     public HttpVersion(
153             String protocolName, int majorVersion, int minorVersion,
154             boolean keepAliveDefault) {
155         this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
156     }
157 
158     private HttpVersion(
159             String protocolName, int majorVersion, int minorVersion,
160             boolean keepAliveDefault, boolean bytes) {
161         if (protocolName == null) {
162             throw new NullPointerException("protocolName");
163         }
164 
165         protocolName = protocolName.trim().toUpperCase();
166         if (protocolName.isEmpty()) {
167             throw new IllegalArgumentException("empty protocolName");
168         }
169 
170         for (int i = 0; i < protocolName.length(); i ++) {
171             if (Character.isISOControl(protocolName.charAt(i)) ||
172                     Character.isWhitespace(protocolName.charAt(i))) {
173                 throw new IllegalArgumentException("invalid character in protocolName");
174             }
175         }
176 
177         if (majorVersion < 0) {
178             throw new IllegalArgumentException("negative majorVersion");
179         }
180         if (minorVersion < 0) {
181             throw new IllegalArgumentException("negative minorVersion");
182         }
183 
184         this.protocolName = protocolName;
185         this.majorVersion = majorVersion;
186         this.minorVersion = minorVersion;
187         text = protocolName + '/' + majorVersion + '.' + minorVersion;
188         this.keepAliveDefault = keepAliveDefault;
189 
190         if (bytes) {
191             this.bytes = text.getBytes(CharsetUtil.US_ASCII);
192         } else {
193             this.bytes = null;
194         }
195     }
196 
197     /**
198      * Returns the name of the protocol such as {@code "HTTP"} in {@code "HTTP/1.0"}.
199      */
200     public String protocolName() {
201         return protocolName;
202     }
203 
204     /**
205      * Returns the name of the protocol such as {@code 1} in {@code "HTTP/1.0"}.
206      */
207     public int majorVersion() {
208         return majorVersion;
209     }
210 
211     /**
212      * Returns the name of the protocol such as {@code 0} in {@code "HTTP/1.0"}.
213      */
214     public int minorVersion() {
215         return minorVersion;
216     }
217 
218     /**
219      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
220      */
221     public String text() {
222         return text;
223     }
224 
225     /**
226      * Returns {@code true} if and only if the connection is kept alive unless
227      * the {@code "Connection"} header is set to {@code "close"} explicitly.
228      */
229     public boolean isKeepAliveDefault() {
230         return keepAliveDefault;
231     }
232 
233     /**
234      * Returns the full protocol version text such as {@code "HTTP/1.0"}.
235      */
236     @Override
237     public String toString() {
238         return text();
239     }
240 
241     @Override
242     public int hashCode() {
243         return (protocolName().hashCode() * 31 + majorVersion()) * 31 +
244                minorVersion();
245     }
246 
247     @Override
248     public boolean equals(Object o) {
249         if (!(o instanceof HttpVersion)) {
250             return false;
251         }
252 
253         HttpVersion that = (HttpVersion) o;
254         return minorVersion() == that.minorVersion() &&
255                majorVersion() == that.majorVersion() &&
256                protocolName().equals(that.protocolName());
257     }
258 
259     @Override
260     public int compareTo(HttpVersion o) {
261         int v = protocolName().compareTo(o.protocolName());
262         if (v != 0) {
263             return v;
264         }
265 
266         v = majorVersion() - o.majorVersion();
267         if (v != 0) {
268             return v;
269         }
270 
271         return minorVersion() - o.minorVersion();
272     }
273 
274     void encode(ByteBuf buf) {
275         if (bytes == null) {
276             HttpHeaders.encodeAscii0(text, buf);
277         } else {
278             buf.writeBytes(bytes);
279         }
280     }
281 }