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.handler.codec.http.HttpConstants.SP;
19  import io.netty.buffer.ByteBuf;
20  import io.netty.util.AsciiString;
21  import io.netty.util.ByteProcessor;
22  import io.netty.util.CharsetUtil;
23  
24  /**
25   * The response code and its description 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 HttpResponseStatus implements Comparable<HttpResponseStatus> {
30  
31      /**
32       * 100 Continue
33       */
34      public static final HttpResponseStatus CONTINUE = newStatus(100, "Continue");
35  
36      /**
37       * 101 Switching Protocols
38       */
39      public static final HttpResponseStatus SWITCHING_PROTOCOLS = newStatus(101, "Switching Protocols");
40  
41      /**
42       * 102 Processing (WebDAV, RFC2518)
43       */
44      public static final HttpResponseStatus PROCESSING = newStatus(102, "Processing");
45  
46      /**
47       * 200 OK
48       */
49      public static final HttpResponseStatus OK = newStatus(200, "OK");
50  
51      /**
52       * 201 Created
53       */
54      public static final HttpResponseStatus CREATED = newStatus(201, "Created");
55  
56      /**
57       * 202 Accepted
58       */
59      public static final HttpResponseStatus ACCEPTED = newStatus(202, "Accepted");
60  
61      /**
62       * 203 Non-Authoritative Information (since HTTP/1.1)
63       */
64      public static final HttpResponseStatus NON_AUTHORITATIVE_INFORMATION =
65              newStatus(203, "Non-Authoritative Information");
66  
67      /**
68       * 204 No Content
69       */
70      public static final HttpResponseStatus NO_CONTENT = newStatus(204, "No Content");
71  
72      /**
73       * 205 Reset Content
74       */
75      public static final HttpResponseStatus RESET_CONTENT = newStatus(205, "Reset Content");
76  
77      /**
78       * 206 Partial Content
79       */
80      public static final HttpResponseStatus PARTIAL_CONTENT = newStatus(206, "Partial Content");
81  
82      /**
83       * 207 Multi-Status (WebDAV, RFC2518)
84       */
85      public static final HttpResponseStatus MULTI_STATUS = newStatus(207, "Multi-Status");
86  
87      /**
88       * 300 Multiple Choices
89       */
90      public static final HttpResponseStatus MULTIPLE_CHOICES = newStatus(300, "Multiple Choices");
91  
92      /**
93       * 301 Moved Permanently
94       */
95      public static final HttpResponseStatus MOVED_PERMANENTLY = newStatus(301, "Moved Permanently");
96  
97      /**
98       * 302 Found
99       */
100     public static final HttpResponseStatus FOUND = newStatus(302, "Found");
101 
102     /**
103      * 303 See Other (since HTTP/1.1)
104      */
105     public static final HttpResponseStatus SEE_OTHER = newStatus(303, "See Other");
106 
107     /**
108      * 304 Not Modified
109      */
110     public static final HttpResponseStatus NOT_MODIFIED = newStatus(304, "Not Modified");
111 
112     /**
113      * 305 Use Proxy (since HTTP/1.1)
114      */
115     public static final HttpResponseStatus USE_PROXY = newStatus(305, "Use Proxy");
116 
117     /**
118      * 307 Temporary Redirect (since HTTP/1.1)
119      */
120     public static final HttpResponseStatus TEMPORARY_REDIRECT = newStatus(307, "Temporary Redirect");
121 
122     /**
123      * 400 Bad Request
124      */
125     public static final HttpResponseStatus BAD_REQUEST = newStatus(400, "Bad Request");
126 
127     /**
128      * 401 Unauthorized
129      */
130     public static final HttpResponseStatus UNAUTHORIZED = newStatus(401, "Unauthorized");
131 
132     /**
133      * 402 Payment Required
134      */
135     public static final HttpResponseStatus PAYMENT_REQUIRED = newStatus(402, "Payment Required");
136 
137     /**
138      * 403 Forbidden
139      */
140     public static final HttpResponseStatus FORBIDDEN = newStatus(403, "Forbidden");
141 
142     /**
143      * 404 Not Found
144      */
145     public static final HttpResponseStatus NOT_FOUND = newStatus(404, "Not Found");
146 
147     /**
148      * 405 Method Not Allowed
149      */
150     public static final HttpResponseStatus METHOD_NOT_ALLOWED = newStatus(405, "Method Not Allowed");
151 
152     /**
153      * 406 Not Acceptable
154      */
155     public static final HttpResponseStatus NOT_ACCEPTABLE = newStatus(406, "Not Acceptable");
156 
157     /**
158      * 407 Proxy Authentication Required
159      */
160     public static final HttpResponseStatus PROXY_AUTHENTICATION_REQUIRED =
161             newStatus(407, "Proxy Authentication Required");
162 
163     /**
164      * 408 Request Timeout
165      */
166     public static final HttpResponseStatus REQUEST_TIMEOUT = newStatus(408, "Request Timeout");
167 
168     /**
169      * 409 Conflict
170      */
171     public static final HttpResponseStatus CONFLICT = newStatus(409, "Conflict");
172 
173     /**
174      * 410 Gone
175      */
176     public static final HttpResponseStatus GONE = newStatus(410, "Gone");
177 
178     /**
179      * 411 Length Required
180      */
181     public static final HttpResponseStatus LENGTH_REQUIRED = newStatus(411, "Length Required");
182 
183     /**
184      * 412 Precondition Failed
185      */
186     public static final HttpResponseStatus PRECONDITION_FAILED = newStatus(412, "Precondition Failed");
187 
188     /**
189      * 413 Request Entity Too Large
190      */
191     public static final HttpResponseStatus REQUEST_ENTITY_TOO_LARGE =
192             newStatus(413, "Request Entity Too Large");
193 
194     /**
195      * 414 Request-URI Too Long
196      */
197     public static final HttpResponseStatus REQUEST_URI_TOO_LONG = newStatus(414, "Request-URI Too Long");
198 
199     /**
200      * 415 Unsupported Media Type
201      */
202     public static final HttpResponseStatus UNSUPPORTED_MEDIA_TYPE = newStatus(415, "Unsupported Media Type");
203 
204     /**
205      * 416 Requested Range Not Satisfiable
206      */
207     public static final HttpResponseStatus REQUESTED_RANGE_NOT_SATISFIABLE =
208             newStatus(416, "Requested Range Not Satisfiable");
209 
210     /**
211      * 417 Expectation Failed
212      */
213     public static final HttpResponseStatus EXPECTATION_FAILED = newStatus(417, "Expectation Failed");
214 
215     /**
216      * 421 Misdirected Request
217      *
218      * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-15#section-9.1.2">421 Status Code</a>
219      */
220     public static final HttpResponseStatus MISDIRECTED_REQUEST = newStatus(421, "Misdirected Request");
221 
222     /**
223      * 422 Unprocessable Entity (WebDAV, RFC4918)
224      */
225     public static final HttpResponseStatus UNPROCESSABLE_ENTITY = newStatus(422, "Unprocessable Entity");
226 
227     /**
228      * 423 Locked (WebDAV, RFC4918)
229      */
230     public static final HttpResponseStatus LOCKED = newStatus(423, "Locked");
231 
232     /**
233      * 424 Failed Dependency (WebDAV, RFC4918)
234      */
235     public static final HttpResponseStatus FAILED_DEPENDENCY = newStatus(424, "Failed Dependency");
236 
237     /**
238      * 425 Unordered Collection (WebDAV, RFC3648)
239      */
240     public static final HttpResponseStatus UNORDERED_COLLECTION = newStatus(425, "Unordered Collection");
241 
242     /**
243      * 426 Upgrade Required (RFC2817)
244      */
245     public static final HttpResponseStatus UPGRADE_REQUIRED = newStatus(426, "Upgrade Required");
246 
247     /**
248      * 428 Precondition Required (RFC6585)
249      */
250     public static final HttpResponseStatus PRECONDITION_REQUIRED = newStatus(428, "Precondition Required");
251 
252     /**
253      * 429 Too Many Requests (RFC6585)
254      */
255     public static final HttpResponseStatus TOO_MANY_REQUESTS = newStatus(429, "Too Many Requests");
256 
257     /**
258      * 431 Request Header Fields Too Large (RFC6585)
259      */
260     public static final HttpResponseStatus REQUEST_HEADER_FIELDS_TOO_LARGE =
261             newStatus(431, "Request Header Fields Too Large");
262 
263     /**
264      * 500 Internal Server Error
265      */
266     public static final HttpResponseStatus INTERNAL_SERVER_ERROR = newStatus(500, "Internal Server Error");
267 
268     /**
269      * 501 Not Implemented
270      */
271     public static final HttpResponseStatus NOT_IMPLEMENTED = newStatus(501, "Not Implemented");
272 
273     /**
274      * 502 Bad Gateway
275      */
276     public static final HttpResponseStatus BAD_GATEWAY = newStatus(502, "Bad Gateway");
277 
278     /**
279      * 503 Service Unavailable
280      */
281     public static final HttpResponseStatus SERVICE_UNAVAILABLE = newStatus(503, "Service Unavailable");
282 
283     /**
284      * 504 Gateway Timeout
285      */
286     public static final HttpResponseStatus GATEWAY_TIMEOUT = newStatus(504, "Gateway Timeout");
287 
288     /**
289      * 505 HTTP Version Not Supported
290      */
291     public static final HttpResponseStatus HTTP_VERSION_NOT_SUPPORTED =
292             newStatus(505, "HTTP Version Not Supported");
293 
294     /**
295      * 506 Variant Also Negotiates (RFC2295)
296      */
297     public static final HttpResponseStatus VARIANT_ALSO_NEGOTIATES = newStatus(506, "Variant Also Negotiates");
298 
299     /**
300      * 507 Insufficient Storage (WebDAV, RFC4918)
301      */
302     public static final HttpResponseStatus INSUFFICIENT_STORAGE = newStatus(507, "Insufficient Storage");
303 
304     /**
305      * 510 Not Extended (RFC2774)
306      */
307     public static final HttpResponseStatus NOT_EXTENDED = newStatus(510, "Not Extended");
308 
309     /**
310      * 511 Network Authentication Required (RFC6585)
311      */
312     public static final HttpResponseStatus NETWORK_AUTHENTICATION_REQUIRED =
313             newStatus(511, "Network Authentication Required");
314 
315     private static HttpResponseStatus newStatus(int statusCode, String reasonPhrase) {
316         return new HttpResponseStatus(statusCode, reasonPhrase, true);
317     }
318 
319     /**
320      * Returns the [email protected] HttpResponseStatus} represented by the specified code.
321      * If the specified code is a standard HTTP getStatus code, a cached instance
322      * will be returned.  Otherwise, a new instance will be returned.
323      */
324     public static HttpResponseStatus valueOf(int code) {
325         switch (code) {
326         case 100:
327             return CONTINUE;
328         case 101:
329             return SWITCHING_PROTOCOLS;
330         case 102:
331             return PROCESSING;
332         case 200:
333             return OK;
334         case 201:
335             return CREATED;
336         case 202:
337             return ACCEPTED;
338         case 203:
339             return NON_AUTHORITATIVE_INFORMATION;
340         case 204:
341             return NO_CONTENT;
342         case 205:
343             return RESET_CONTENT;
344         case 206:
345             return PARTIAL_CONTENT;
346         case 207:
347             return MULTI_STATUS;
348         case 300:
349             return MULTIPLE_CHOICES;
350         case 301:
351             return MOVED_PERMANENTLY;
352         case 302:
353             return FOUND;
354         case 303:
355             return SEE_OTHER;
356         case 304:
357             return NOT_MODIFIED;
358         case 305:
359             return USE_PROXY;
360         case 307:
361             return TEMPORARY_REDIRECT;
362         case 400:
363             return BAD_REQUEST;
364         case 401:
365             return UNAUTHORIZED;
366         case 402:
367             return PAYMENT_REQUIRED;
368         case 403:
369             return FORBIDDEN;
370         case 404:
371             return NOT_FOUND;
372         case 405:
373             return METHOD_NOT_ALLOWED;
374         case 406:
375             return NOT_ACCEPTABLE;
376         case 407:
377             return PROXY_AUTHENTICATION_REQUIRED;
378         case 408:
379             return REQUEST_TIMEOUT;
380         case 409:
381             return CONFLICT;
382         case 410:
383             return GONE;
384         case 411:
385             return LENGTH_REQUIRED;
386         case 412:
387             return PRECONDITION_FAILED;
388         case 413:
389             return REQUEST_ENTITY_TOO_LARGE;
390         case 414:
391             return REQUEST_URI_TOO_LONG;
392         case 415:
393             return UNSUPPORTED_MEDIA_TYPE;
394         case 416:
395             return REQUESTED_RANGE_NOT_SATISFIABLE;
396         case 417:
397             return EXPECTATION_FAILED;
398         case 421:
399             return MISDIRECTED_REQUEST;
400         case 422:
401             return UNPROCESSABLE_ENTITY;
402         case 423:
403             return LOCKED;
404         case 424:
405             return FAILED_DEPENDENCY;
406         case 425:
407             return UNORDERED_COLLECTION;
408         case 426:
409             return UPGRADE_REQUIRED;
410         case 428:
411             return PRECONDITION_REQUIRED;
412         case 429:
413             return TOO_MANY_REQUESTS;
414         case 431:
415             return REQUEST_HEADER_FIELDS_TOO_LARGE;
416         case 500:
417             return INTERNAL_SERVER_ERROR;
418         case 501:
419             return NOT_IMPLEMENTED;
420         case 502:
421             return BAD_GATEWAY;
422         case 503:
423             return SERVICE_UNAVAILABLE;
424         case 504:
425             return GATEWAY_TIMEOUT;
426         case 505:
427             return HTTP_VERSION_NOT_SUPPORTED;
428         case 506:
429             return VARIANT_ALSO_NEGOTIATES;
430         case 507:
431             return INSUFFICIENT_STORAGE;
432         case 510:
433             return NOT_EXTENDED;
434         case 511:
435             return NETWORK_AUTHENTICATION_REQUIRED;
436         }
437 
438         return new HttpResponseStatus(code);
439     }
440 
441     /**
442      * Parses the specified HTTP status line into a [email protected] HttpResponseStatus}.  The expected formats of the line are:
443      * <ul>
444      * <li>[email protected] statusCode} (e.g. 200)</li>
445      * <li>[email protected] statusCode} [email protected] reasonPhrase} (e.g. 404 Not Found)</li>
446      * </ul>
447      *
448      * @throws IllegalArgumentException if the specified status line is malformed
449      */
450     public static HttpResponseStatus parseLine(CharSequence line) {
451         String status = line.toString();
452         try {
453             int space = status.indexOf(' ');
454             if (space == -1) {
455                 return valueOf(Integer.parseInt(status));
456             } else {
457                 int code = Integer.parseInt(status.substring(0, space));
458                 String reasonPhrase = status.substring(space + 1);
459                 HttpResponseStatus responseStatus = valueOf(code);
460                 if (responseStatus.reasonPhrase().contentEquals(reasonPhrase)) {
461                     return responseStatus;
462                 } else {
463                     return new HttpResponseStatus(code, reasonPhrase);
464                 }
465             }
466         } catch (Exception e) {
467             throw new IllegalArgumentException("malformed status line: " + status, e);
468         }
469     }
470 
471     private static final class HttpStatusLineProcessor implements ByteProcessor {
472         private static final byte ASCII_SPACE = (byte) ' ';
473         private final AsciiString string;
474         private int i;
475         /**
476          * 0 = New or havn't seen [email protected] #ASCII_SPACE}.
477          * 1 = Last byte was [email protected] #ASCII_SPACE}.
478          * 2 = Terminal State. Processed the byte after [email protected] #ASCII_SPACE}, and parsed the status line.
479          * 3 = Terminal State. There was no byte after [email protected] #ASCII_SPACE} but status has been parsed with what we saw.
480          */
481         private int state;
482         private HttpResponseStatus status;
483 
484         public HttpStatusLineProcessor(AsciiString string) {
485             this.string = string;
486         }
487 
488         @Override
489         public boolean process(byte value) {
490             switch (state) {
491             case 0:
492                 if (value == ASCII_SPACE) {
493                     state = 1;
494                 }
495                 break;
496             case 1:
497                 parseStatus(i);
498                 state = 2;
499                 return false;
500             default:
501                 break;
502             }
503             ++i;
504             return true;
505         }
506 
507         private void parseStatus(int codeEnd) {
508             int code = string.parseInt(0, codeEnd);
509             status = valueOf(code);
510             if (codeEnd < string.length()) {
511                 String actualReason = string.toString(codeEnd + 1, string.length());
512                 if (!status.reasonPhrase().contentEquals(actualReason)) {
513                     status = new HttpResponseStatus(code, actualReason);
514                 }
515             }
516         }
517 
518         public HttpResponseStatus status() {
519             if (state <= 1) {
520                 parseStatus(string.length());
521                 state = 3;
522             }
523             return status;
524         }
525     }
526 
527     /**
528      * Parses the specified HTTP status line into a [email protected] HttpResponseStatus}. The expected formats of the line are:
529      * <ul>
530      * <li>[email protected] statusCode} (e.g. 200)</li>
531      * <li>[email protected] statusCode} [email protected] reasonPhrase} (e.g. 404 Not Found)</li>
532      * </ul>
533      *
534      * @throws IllegalArgumentException if the specified status line is malformed
535      */
536     public static HttpResponseStatus parseLine(AsciiString line) {
537         try {
538             HttpStatusLineProcessor processor = new HttpStatusLineProcessor(line);
539             line.forEachByte(processor);
540             HttpResponseStatus status = processor.status();
541             if (status == null) {
542                 throw new IllegalArgumentException("unable to get status after parsing input");
543             }
544             return status;
545         } catch (Exception e) {
546             throw new IllegalArgumentException("malformed status line: " + line, e);
547         }
548     }
549 
550     private final int code;
551     private final AsciiString codeAsText;
552     private HttpStatusClass codeClass;
553 
554     private final String reasonPhrase;
555     private final byte[] bytes;
556 
557     /**
558      * Creates a new instance with the specified [email protected] code} and the auto-generated default reason phrase.
559      */
560     private HttpResponseStatus(int code) {
561         this(code, HttpStatusClass.valueOf(code).defaultReasonPhrase() + " (" + code + ')', false);
562     }
563 
564     /**
565      * Creates a new instance with the specified [email protected] code} and its [email protected] reasonPhrase}.
566      */
567     public HttpResponseStatus(int code, String reasonPhrase) {
568         this(code, reasonPhrase, false);
569     }
570 
571     private HttpResponseStatus(int code, String reasonPhrase, boolean bytes) {
572         if (code < 0) {
573             throw new IllegalArgumentException(
574                     "code: " + code + " (expected: 0+)");
575         }
576 
577         if (reasonPhrase == null) {
578             throw new NullPointerException("reasonPhrase");
579         }
580 
581         for (int i = 0; i < reasonPhrase.length(); i ++) {
582             char c = reasonPhrase.charAt(i);
583             // Check prohibited characters.
584             switch (c) {
585                 case '\n': case '\r':
586                     throw new IllegalArgumentException(
587                             "reasonPhrase contains one of the following prohibited characters: " +
588                                     "\\r\\n: " + reasonPhrase);
589             }
590         }
591 
592         this.code = code;
593         codeAsText = new AsciiString(Integer.toString(code));
594         this.reasonPhrase = reasonPhrase;
595         if (bytes) {
596             this.bytes = (code + " " + reasonPhrase).getBytes(CharsetUtil.US_ASCII);
597         } else {
598             this.bytes = null;
599         }
600     }
601 
602     /**
603      * Returns the code of this [email protected] HttpResponseStatus}.
604      */
605     public int code() {
606         return code;
607     }
608 
609     /**
610      * Returns the status code as [email protected] AsciiString}.
611      */
612     public AsciiString codeAsText() {
613         return codeAsText;
614     }
615 
616     /**
617      * Returns the reason phrase of this [email protected] HttpResponseStatus}.
618      */
619     public String reasonPhrase() {
620         return reasonPhrase;
621     }
622 
623     /**
624      * Returns the class of this [email protected] HttpResponseStatus}
625      */
626     public HttpStatusClass codeClass() {
627         HttpStatusClass type = this.codeClass;
628         if (type == null) {
629             this.codeClass = type = HttpStatusClass.valueOf(code);
630         }
631         return type;
632     }
633 
634     @Override
635     public int hashCode() {
636         return code();
637     }
638 
639     /**
640      * Equality of [email protected] HttpResponseStatus} only depends on [email protected] #code()}. The
641      * reason phrase is not considered for equality.
642      */
643     @Override
644     public boolean equals(Object o) {
645         if (!(o instanceof HttpResponseStatus)) {
646             return false;
647         }
648 
649         return code() == ((HttpResponseStatus) o).code();
650     }
651 
652     /**
653      * Equality of [email protected] HttpResponseStatus} only depends on [email protected] #code()}. The
654      * reason phrase is not considered for equality.
655      */
656     @Override
657     public int compareTo(HttpResponseStatus o) {
658         return code() - o.code();
659     }
660 
661     @Override
662     public String toString() {
663         return new StringBuilder(reasonPhrase.length() + 5)
664             .append(code)
665             .append(' ')
666             .append(reasonPhrase)
667             .toString();
668     }
669 
670     void encode(ByteBuf buf) {
671         if (bytes == null) {
672             HttpUtil.encodeAscii0(String.valueOf(code()), buf);
673             buf.writeByte(SP);
674             HttpUtil.encodeAscii0(String.valueOf(reasonPhrase()), buf);
675         } else {
676             buf.writeBytes(bytes);
677         }
678     }
679 }