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