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