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