[Python-checkins] cpython: Issue #21793: Added http.HTTPStatus enums (i.e. HTTPStatus.OK,

serhiy.storchaka python-checkins at python.org
Tue Dec 23 15:30:14 CET 2014


https://hg.python.org/cpython/rev/edf669b13482
changeset:   93954:edf669b13482
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Tue Dec 23 16:28:28 2014 +0200
summary:
  Issue #21793: Added http.HTTPStatus enums (i.e. HTTPStatus.OK,
HTTPStatus.NOT_FOUND).  Patch by Demian Brecht.

files:
  Doc/library/http.rst |  121 ++++++++++++++++++++++-
  Doc/whatsnew/3.5.rst |    5 +
  Lib/http/__init__.py |  165 ++++++++++++++++++++++++++++++-
  Lib/http/client.py   |  120 +---------------------
  Lib/http/server.py   |  153 ++++++++++------------------
  Misc/NEWS            |    3 +
  6 files changed, 352 insertions(+), 215 deletions(-)


diff --git a/Doc/library/http.rst b/Doc/library/http.rst
--- a/Doc/library/http.rst
+++ b/Doc/library/http.rst
@@ -1,7 +1,16 @@
 :mod:`http` --- HTTP modules
 ============================
 
-``http`` is a package that collects several modules for working with the
+.. module:: http
+   :synopsis: HTTP status codes and messages
+
+.. index::
+   pair: HTTP; protocol
+   single: HTTP; http (standard module)
+
+**Source code:** :source:`Lib/http/__init__.py`
+
+:mod:`http` is a also package that collects several modules for working with the
 HyperText Transfer Protocol:
 
 * :mod:`http.client` is a low-level HTTP protocol client; for high-level URL
@@ -9,3 +18,113 @@
 * :mod:`http.server` contains basic HTTP server classes based on :mod:`socketserver`
 * :mod:`http.cookies` has utilities for implementing state management with cookies
 * :mod:`http.cookiejar` provides persistence of cookies
+
+:mod:`http` is also a module that defines a number of HTTP status codes and
+associated messages through the :class:`http.HTTPStatus` enum:
+
+.. class:: HTTPStatus
+
+   A subclass of :class:`enum.IntEnum` that defines a set of HTTP status codes,
+   reason phrases and long descriptions written in English.
+
+   Usage::
+
+      >>> from http import HTTPStatus
+      >>> HTTPStatus.OK
+      <HTTPStatus.OK: 200>
+      >>> HTTPStatus.OK == 200
+      True
+      >>> http.HTTPStatus.OK.value
+      200
+      >>> HTTPStatus.OK.phrase
+      'OK'
+      >>> HTTPStatus.OK.description
+      'Request fulfilled, document follows'
+      >>> list(HTTPStatus)
+      [<HTTPStatus.CONTINUE: 100>, <HTTPStatus.SWITCHING_PROTOCOLS: 101>, ...]
+
+   .. versionadded:: 3.5
+      Added the *HTTPStatus* Enum
+
+   The supported HTTP status codes are:
+
+   === ==============================
+   100 Continue
+   101 Switching Protocols
+   102 Processing
+   200 OK
+   201 Created
+   202 Accepted
+   203 Non-Authoritative Information
+   204 No Content
+   205 Reset Content
+   206 Partial Content
+   207 Multi-Status
+   208 Already Reported
+   226 IM Used
+   300 Multiple Choices
+   301 Moved Permanently
+   302 Found
+   303 See Other
+   304 Not Modified
+   305 Use Proxy
+   306 Switch Proxy
+   307 Temporary Redirect
+   308 Permanent Redirect
+   400 Bad Request
+   401 Unauthorized
+   402 Payment Required
+   403 Forbidden
+   404 Not Found
+   405 Method Not Allowed
+   406 Not Acceptable
+   407 Proxy Authentication Required
+   408 Request Timeout
+   409 Conflict
+   410 Gone
+   411 Length Required
+   412 Precondition Failed
+   413 Request Entity Too Large
+   414 Request URI Too Long
+   415 Unsupported Media Type
+   416 Request Range Not Satisfiable
+   417 Expectation Failed
+   418 I'm a teapot
+   419 Authentication Timeout
+   420 Method Failure *(Spring framework)*
+   422 Unprocessable Entity
+   423 Locked
+   424 Failed Dependency
+   426 Upgrade Required
+   428 Precondition Required
+   429 Too Many Requests
+   431 Request Header Field Too Large
+   440 Login Timeout *(Microsoft)*
+   444 No Response *(Nginx)*
+   449 Retry With *(Microsoft)*
+   450 Blocked By Windows Parental Controls *(Microsoft)*
+   494 Request Header Too Large *(Nginx)*
+   495 Cert Error *(Nginx)*
+   496 No Cert *(Nginx)*
+   497 HTTP To HTTPS *(Nginx)*
+   499 Client Closed Request *(Nginx)*
+   500 Internal Server Error
+   501 Not Implemented
+   502 Bad Gateway
+   503 Service Unavailable
+   504 Gateway Timeout
+   505 HTTP Version Not Supported
+   506 Variant Also Negotiates
+   507 Insufficient Storage
+   508 Loop Detected
+   509 Bandwidth Limit Exceeded
+   510 Not Extended
+   511 Network Authentication Required
+   520 Origin Error *(CloudFlare)*
+   521 Web Server Is Down *(CloudFlare)*
+   522 Connection Timed Out *(CloudFlare)*
+   523 Proxy Declined Request *(CloudFlare)*
+   524 A Timeout Occurred *(CloudFlare)*
+   598 Network Read Timeout Error
+   599 Network Connect Timeout Error
+   === ==============================
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -451,6 +451,11 @@
   **without** caching ``None`` in :data:`sys.path_importer_cache` which is
   different than the typical case (:issue:`22834`).
 
+* HTTP status code and messages from `http.client` and `http.server` were
+  refactored into a common :class:`~http.HTTPStatus` enum.  The values in
+  `http.client` and `http.server` remain available for backwards compatibility.
+  (Contributed by Demian Brecht in :issue:`21793`.)
+
 Changes in the C API
 --------------------
 
diff --git a/Lib/http/__init__.py b/Lib/http/__init__.py
--- a/Lib/http/__init__.py
+++ b/Lib/http/__init__.py
@@ -1,1 +1,164 @@
-# This directory is a Python package.
+from enum import IntEnum
+
+__all__ = ['HTTPStatus']
+
+class HTTPStatus(IntEnum):
+    """HTTP status codes and reason phrases
+
+    Status codes from the following RFCs are all observed:
+
+        * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616
+        * RFC 6585: Additional HTTP Status Codes
+        * RFC 3229: Delta encoding in HTTP
+        * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518
+        * RFC 5842: Binding Extensions to WebDAV
+        * RFC 7238: Permanent Redirect
+        * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)
+        * RFC 2295: Transparent Content Negotiation in HTTP
+        * RFC 2774: An HTTP Extension Framework
+
+    Non-standard vendor codes include:
+
+        * Spring framework: 420
+        * Nginx: 444, 494, 495, 496, 497, 499
+        * Microsoft: 440, 449, 450
+        * Cloudflare: 520, 521, 522, 523, 524, 598, 599
+    """
+    def __new__(cls, value, phrase, description=''):
+        obj = int.__new__(cls, value)
+        obj._value_ = value
+
+        obj.phrase = phrase
+        obj.description = description
+        return obj
+
+    # informational
+    CONTINUE = 100, 'Continue', 'Request received, please continue'
+    SWITCHING_PROTOCOLS = (101, 'Switching Protocols',
+            'Switching to new protocol; obey Upgrade header')
+    PROCESSING = 102, 'Processing'
+
+    # success
+    OK = 200, 'OK', 'Request fulfilled, document follows'
+    CREATED = 201, 'Created', 'Document created, URL follows'
+    ACCEPTED = (202, 'Accepted',
+        'Request accepted, processing continues off-line')
+    NON_AUTHORITATIVE_INFORMATION = (203,
+        'Non-Authoritative Information', 'Request fulfilled from cache')
+    NO_CONTENT = 204, 'No Content', 'Request fulfilled, nothing follows'
+    RESET_CONTENT = 205, 'Reset Content', 'Clear input form for further input'
+    PARTIAL_CONTENT = 206, 'Partial Content', 'Partial content follows'
+    MULTI_STATUS = 207, 'Multi-Status'
+    ALREADY_REPORTED = 208, 'Already Reported'
+    IM_USED = 226, 'IM Used'
+
+    # redirection
+    MULTIPLE_CHOICES = (300, 'Multiple Choices',
+        'Object has several resources -- see URI list')
+    MOVED_PERMANENTLY = (301, 'Moved Permanently',
+        'Object moved permanently -- see URI list')
+    FOUND = 302, 'Found', 'Object moved temporarily -- see URI list'
+    SEE_OTHER = 303, 'See Other', 'Object moved -- see Method and URL list'
+    NOT_MODIFIED = (304, 'Not Modified',
+        'Document has not changed since given time')
+    USE_PROXY = (305, 'Use Proxy',
+        'You must use proxy specified in Location to access this resource')
+    SWITCH_PROXY = 306, 'Switch Proxy'
+    TEMPORARY_REDIRECT = (307, 'Temporary Redirect',
+        'Object moved temporarily -- see URI list')
+    PERMANENT_REDIRECT = (308, 'Permanent Redirect',
+        'Object moved temporarily -- see URI list')
+
+    # client error
+    BAD_REQUEST = (400, 'Bad Request',
+        'Bad request syntax or unsupported method')
+    UNAUTHORIZED = (401, 'Unauthorized',
+        'No permission -- see authorization schemes')
+    PAYMENT_REQUIRED = (402, 'Payment Required',
+        'No payment -- see charging schemes')
+    FORBIDDEN = (403, 'Forbidden',
+        'Request forbidden -- authorization will not help')
+    NOT_FOUND = (404, 'Not Found',
+        'Nothing matches the given URI')
+    METHOD_NOT_ALLOWED = (405, 'Method Not Allowed',
+        'Specified method is invalid for this resource')
+    NOT_ACCEPTABLE = (406, 'Not Acceptable',
+        'URI not available in preferred format')
+    PROXY_AUTHENTICATION_REQUIRED = (407,
+        'Proxy Authentication Required',
+        'You must authenticate with this proxy before proceeding')
+    REQUEST_TIMEOUT = (408, 'Request Timeout',
+        'Request timed out; try again later')
+    CONFLICT = 409, 'Conflict', 'Request conflict'
+    GONE = (410, 'Gone',
+        'URI no longer exists and has been permanently removed')
+    LENGTH_REQUIRED = (411, 'Length Required',
+        'Client must specify Content-Length')
+    PRECONDITION_FAILED = (412, 'Precondition Failed',
+        'Precondition in headers is false')
+    REQUEST_ENTITY_TOO_LARGE = (413, 'Request Entity Too Large',
+        'Entity is too large')
+    REQUEST_URI_TOO_LONG = (414, 'Request-URI Too Long',
+        'URI is too long')
+    UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type',
+        'Entity body in unsupported format')
+    REQUEST_RANGE_NOT_SATISFIABLE = (416,
+        'Request Range Not Satisfiable',
+        'Cannot satisfy request range')
+    EXPECTATION_FAILED = (417, 'Expectation Failed',
+        'Expect condition could not be satisfied')
+    IM_A_TEAPOT = 418, 'I\'m a teapot'
+    AUTHENTICATION_TIMEOUT = 419, 'Authentication Timeout'
+    METHOD_FAILURE = 420, 'Method Failure' # Spring framework
+    UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity'
+    LOCKED = 423, 'Locked'
+    FAILED_DEPENDENCY = 424, 'Failed Dependency'
+    UPGRADE_REQUIRED = 426, 'Upgrade Required'
+    PRECONDITION_REQUIRED = (428, 'Precondition Required',
+        'The origin server requires the request to be conditional')
+    TOO_MANY_REQUESTS = (429, 'Too Many Requests',
+        'The user has sent too many requests in '
+        'a given amount of time ("rate limiting")')
+    REQUEST_HEADER_FIELD_TOO_LARGE = (431,
+        'Request Header Field Too Large',
+        'The server is unwilling to process the request because its header '
+        'fields are too large')
+    LOGIN_TIMEOUT = 440, 'Login Timeout'  # microsoft
+    NO_RESPONSE = 444, 'No Response'  # nginx
+    RETRY_WITH = 449, 'Retry With'  # microsoft
+    BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS = (450,
+        'Blocked By Windows Parental Controls')  # microsoft
+    REQUEST_HEADER_TOO_LARGE = 494, 'Request Header Too Large'  # nginx
+    CERT_ERROR = 495, 'Cert Error'  # nginx
+    NO_CERT = 496, 'No Cert'  # nginx
+    HTTP_TO_HTTPS = 497, 'HTTP To HTTPS'  # nginx
+    CLIENT_CLOSED_REQUEST = 499, 'Client Closed Request'  # nginx
+
+    # server errors
+    INTERNAL_SERVER_ERROR = (500, 'Internal Server Error',
+        'Server got itself in trouble')
+    NOT_IMPLEMENTED = (501, 'Not Implemented',
+        'Server does not support this operation')
+    BAD_GATEWAY = (502, 'Bad Gateway',
+        'Invalid responses from another server/proxy')
+    SERVICE_UNAVAILABLE = (503, 'Service Unavailable',
+        'The server cannot process the request due to a high load')
+    GATEWAY_TIMEOUT = (504, 'Gateway Timeout',
+        'The gateway server did not receive a timely response')
+    HTTP_VERSION_NOT_SUPPORTED = (505, 'HTTP Version Not Supported',
+        'Cannot fulfill request')
+    VARIANT_ALSO_NEGOTIATES = 506, 'Variant Also Negotiates'
+    INSUFFICIENT_STORAGE = 507, 'Insufficient Storage'
+    LOOP_DETECTED = 508, 'Loop Detected'
+    BANDWIDTH_LIMIT_EXCEEDED = 509, 'Bandwidth Limit Exceeded'
+    NOT_EXTENDED = 510, 'Not Extended'
+    NETWORK_AUTHENTICATION_REQUIRED = (511,
+        'Network Authentication Required',
+        'The client needs to authenticate to gain network access')
+    ORIGIN_ERROR = 520, 'Origin Error'  # cloudflare
+    WEB_SERVER_IS_DOWN = 521, 'Web Server Is Down'  # cloudflare
+    CONNECTON_TIMED_OUT = 522, 'Connection Timed Out'  # cloudflare
+    PROXY_DECLINED_REQUEST = 523, 'Proxy Declined Request'  # cloudflare
+    A_TIMEOUT_OCCURRED = 524, 'A Timeout Occurred', ''  # cloudflare
+    NETWORK_READ_TIMEOUT_ERROR = 598, 'Network Read Timeout Error'
+    NETWORK_CONNECT_TIMEOUT_ERROR = 599, 'Network Connect Timeout Error'
diff --git a/Lib/http/client.py b/Lib/http/client.py
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -68,6 +68,7 @@
 
 import email.parser
 import email.message
+import http
 import io
 import os
 import socket
@@ -91,122 +92,13 @@
 _CS_REQ_STARTED = 'Request-started'
 _CS_REQ_SENT = 'Request-sent'
 
-# status codes
-# informational
-CONTINUE = 100
-SWITCHING_PROTOCOLS = 101
-PROCESSING = 102
 
-# successful
-OK = 200
-CREATED = 201
-ACCEPTED = 202
-NON_AUTHORITATIVE_INFORMATION = 203
-NO_CONTENT = 204
-RESET_CONTENT = 205
-PARTIAL_CONTENT = 206
-MULTI_STATUS = 207
-IM_USED = 226
+# hack to maintain backwards compatibility
+globals().update(http.HTTPStatus.__members__)
 
-# redirection
-MULTIPLE_CHOICES = 300
-MOVED_PERMANENTLY = 301
-FOUND = 302
-SEE_OTHER = 303
-NOT_MODIFIED = 304
-USE_PROXY = 305
-TEMPORARY_REDIRECT = 307
-
-# client error
-BAD_REQUEST = 400
-UNAUTHORIZED = 401
-PAYMENT_REQUIRED = 402
-FORBIDDEN = 403
-NOT_FOUND = 404
-METHOD_NOT_ALLOWED = 405
-NOT_ACCEPTABLE = 406
-PROXY_AUTHENTICATION_REQUIRED = 407
-REQUEST_TIMEOUT = 408
-CONFLICT = 409
-GONE = 410
-LENGTH_REQUIRED = 411
-PRECONDITION_FAILED = 412
-REQUEST_ENTITY_TOO_LARGE = 413
-REQUEST_URI_TOO_LONG = 414
-UNSUPPORTED_MEDIA_TYPE = 415
-REQUESTED_RANGE_NOT_SATISFIABLE = 416
-EXPECTATION_FAILED = 417
-UNPROCESSABLE_ENTITY = 422
-LOCKED = 423
-FAILED_DEPENDENCY = 424
-UPGRADE_REQUIRED = 426
-PRECONDITION_REQUIRED = 428
-TOO_MANY_REQUESTS = 429
-REQUEST_HEADER_FIELDS_TOO_LARGE = 431
-
-# server error
-INTERNAL_SERVER_ERROR = 500
-NOT_IMPLEMENTED = 501
-BAD_GATEWAY = 502
-SERVICE_UNAVAILABLE = 503
-GATEWAY_TIMEOUT = 504
-HTTP_VERSION_NOT_SUPPORTED = 505
-INSUFFICIENT_STORAGE = 507
-NOT_EXTENDED = 510
-NETWORK_AUTHENTICATION_REQUIRED = 511
-
+# another hack to maintain backwards compatibility
 # Mapping status codes to official W3C names
-responses = {
-    100: 'Continue',
-    101: 'Switching Protocols',
-
-    200: 'OK',
-    201: 'Created',
-    202: 'Accepted',
-    203: 'Non-Authoritative Information',
-    204: 'No Content',
-    205: 'Reset Content',
-    206: 'Partial Content',
-
-    300: 'Multiple Choices',
-    301: 'Moved Permanently',
-    302: 'Found',
-    303: 'See Other',
-    304: 'Not Modified',
-    305: 'Use Proxy',
-    306: '(Unused)',
-    307: 'Temporary Redirect',
-
-    400: 'Bad Request',
-    401: 'Unauthorized',
-    402: 'Payment Required',
-    403: 'Forbidden',
-    404: 'Not Found',
-    405: 'Method Not Allowed',
-    406: 'Not Acceptable',
-    407: 'Proxy Authentication Required',
-    408: 'Request Timeout',
-    409: 'Conflict',
-    410: 'Gone',
-    411: 'Length Required',
-    412: 'Precondition Failed',
-    413: 'Request Entity Too Large',
-    414: 'Request-URI Too Long',
-    415: 'Unsupported Media Type',
-    416: 'Requested Range Not Satisfiable',
-    417: 'Expectation Failed',
-    428: 'Precondition Required',
-    429: 'Too Many Requests',
-    431: 'Request Header Fields Too Large',
-
-    500: 'Internal Server Error',
-    501: 'Not Implemented',
-    502: 'Bad Gateway',
-    503: 'Service Unavailable',
-    504: 'Gateway Timeout',
-    505: 'HTTP Version Not Supported',
-    511: 'Network Authentication Required',
-}
+responses = {v: v.phrase for v in http.HTTPStatus.__members__.values()}
 
 # maximal amount of data to read at one time in _safe_read
 MAXAMOUNT = 1048576
@@ -878,7 +770,7 @@
         response = self.response_class(self.sock, method=self._method)
         (version, code, message) = response._read_status()
 
-        if code != 200:
+        if code != http.HTTPStatus.OK:
             self.close()
             raise OSError("Tunnel connection failed: %d %s" % (code,
                                                                message.strip()))
diff --git a/Lib/http/server.py b/Lib/http/server.py
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -100,6 +100,8 @@
 import copy
 import argparse
 
+from http import HTTPStatus
+
 
 # Default error message template
 DEFAULT_ERROR_MESSAGE = """\
@@ -278,7 +280,9 @@
         if len(words) == 3:
             command, path, version = words
             if version[:5] != 'HTTP/':
-                self.send_error(400, "Bad request version (%r)" % version)
+                self.send_error(
+                    HTTPStatus.BAD_REQUEST,
+                    "Bad request version (%r)" % version)
                 return False
             try:
                 base_version_number = version.split('/', 1)[1]
@@ -293,25 +297,31 @@
                     raise ValueError
                 version_number = int(version_number[0]), int(version_number[1])
             except (ValueError, IndexError):
-                self.send_error(400, "Bad request version (%r)" % version)
+                self.send_error(
+                    HTTPStatus.BAD_REQUEST,
+                    "Bad request version (%r)" % version)
                 return False
             if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
                 self.close_connection = 0
             if version_number >= (2, 0):
-                self.send_error(505,
-                          "Invalid HTTP Version (%s)" % base_version_number)
+                self.send_error(
+                    HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
+                    "Invalid HTTP Version (%s)" % base_version_number)
                 return False
         elif len(words) == 2:
             command, path = words
             self.close_connection = 1
             if command != 'GET':
-                self.send_error(400,
-                                "Bad HTTP/0.9 request type (%r)" % command)
+                self.send_error(
+                    HTTPStatus.BAD_REQUEST,
+                    "Bad HTTP/0.9 request type (%r)" % command)
                 return False
         elif not words:
             return False
         else:
-            self.send_error(400, "Bad request syntax (%r)" % requestline)
+            self.send_error(
+                HTTPStatus.BAD_REQUEST,
+                "Bad request syntax (%r)" % requestline)
             return False
         self.command, self.path, self.request_version = command, path, version
 
@@ -320,7 +330,9 @@
             self.headers = http.client.parse_headers(self.rfile,
                                                      _class=self.MessageClass)
         except http.client.LineTooLong:
-            self.send_error(400, "Line too long")
+            self.send_error(
+                HTTPStatus.BAD_REQUEST,
+                "Line too long")
             return False
 
         conntype = self.headers.get('Connection', "")
@@ -352,7 +364,7 @@
         False.
 
         """
-        self.send_response_only(100)
+        self.send_response_only(HTTPStatus.CONTINUE)
         self.end_headers()
         return True
 
@@ -370,7 +382,7 @@
                 self.requestline = ''
                 self.request_version = ''
                 self.command = ''
-                self.send_error(414)
+                self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
                 return
             if not self.raw_requestline:
                 self.close_connection = 1
@@ -380,7 +392,9 @@
                 return
             mname = 'do_' + self.command
             if not hasattr(self, mname):
-                self.send_error(501, "Unsupported method (%r)" % self.command)
+                self.send_error(
+                    HTTPStatus.NOT_IMPLEMENTED,
+                    "Unsupported method (%r)" % self.command)
                 return
             method = getattr(self, mname)
             method()
@@ -435,7 +449,11 @@
         self.send_header('Connection', 'close')
         self.send_header('Content-Length', int(len(body)))
         self.end_headers()
-        if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
+
+        if (self.command != 'HEAD' and
+                code >= 200 and
+                code not in (
+                    HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED)):
             self.wfile.write(body)
 
     def send_response(self, code, message=None):
@@ -579,82 +597,11 @@
     # MessageClass used to parse headers
     MessageClass = http.client.HTTPMessage
 
-    # Table mapping response codes to messages; entries have the
-    # form {code: (shortmessage, longmessage)}.
-    # See RFC 2616 and 6585.
+    # hack to maintain backwards compatibility
     responses = {
-        100: ('Continue', 'Request received, please continue'),
-        101: ('Switching Protocols',
-              'Switching to new protocol; obey Upgrade header'),
-
-        200: ('OK', 'Request fulfilled, document follows'),
-        201: ('Created', 'Document created, URL follows'),
-        202: ('Accepted',
-              'Request accepted, processing continues off-line'),
-        203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
-        204: ('No Content', 'Request fulfilled, nothing follows'),
-        205: ('Reset Content', 'Clear input form for further input.'),
-        206: ('Partial Content', 'Partial content follows.'),
-
-        300: ('Multiple Choices',
-              'Object has several resources -- see URI list'),
-        301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
-        302: ('Found', 'Object moved temporarily -- see URI list'),
-        303: ('See Other', 'Object moved -- see Method and URL list'),
-        304: ('Not Modified',
-              'Document has not changed since given time'),
-        305: ('Use Proxy',
-              'You must use proxy specified in Location to access this '
-              'resource.'),
-        307: ('Temporary Redirect',
-              'Object moved temporarily -- see URI list'),
-
-        400: ('Bad Request',
-              'Bad request syntax or unsupported method'),
-        401: ('Unauthorized',
-              'No permission -- see authorization schemes'),
-        402: ('Payment Required',
-              'No payment -- see charging schemes'),
-        403: ('Forbidden',
-              'Request forbidden -- authorization will not help'),
-        404: ('Not Found', 'Nothing matches the given URI'),
-        405: ('Method Not Allowed',
-              'Specified method is invalid for this resource.'),
-        406: ('Not Acceptable', 'URI not available in preferred format.'),
-        407: ('Proxy Authentication Required', 'You must authenticate with '
-              'this proxy before proceeding.'),
-        408: ('Request Timeout', 'Request timed out; try again later.'),
-        409: ('Conflict', 'Request conflict.'),
-        410: ('Gone',
-              'URI no longer exists and has been permanently removed.'),
-        411: ('Length Required', 'Client must specify Content-Length.'),
-        412: ('Precondition Failed', 'Precondition in headers is false.'),
-        413: ('Request Entity Too Large', 'Entity is too large.'),
-        414: ('Request-URI Too Long', 'URI is too long.'),
-        415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
-        416: ('Requested Range Not Satisfiable',
-              'Cannot satisfy request range.'),
-        417: ('Expectation Failed',
-              'Expect condition could not be satisfied.'),
-        428: ('Precondition Required',
-              'The origin server requires the request to be conditional.'),
-        429: ('Too Many Requests', 'The user has sent too many requests '
-              'in a given amount of time ("rate limiting").'),
-        431: ('Request Header Fields Too Large', 'The server is unwilling to '
-              'process the request because its header fields are too large.'),
-
-        500: ('Internal Server Error', 'Server got itself in trouble'),
-        501: ('Not Implemented',
-              'Server does not support this operation'),
-        502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
-        503: ('Service Unavailable',
-              'The server cannot process the request due to a high load'),
-        504: ('Gateway Timeout',
-              'The gateway server did not receive a timely response'),
-        505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
-        511: ('Network Authentication Required',
-              'The client needs to authenticate to gain network access.'),
-        }
+        v: (v.phrase, v.description)
+        for v in HTTPStatus.__members__.values()
+    }
 
 
 class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
@@ -703,7 +650,7 @@
         if os.path.isdir(path):
             if not self.path.endswith('/'):
                 # redirect browser - doing basically what apache does
-                self.send_response(301)
+                self.send_response(HTTPStatus.MOVED_PERMANENTLY)
                 self.send_header("Location", self.path + "/")
                 self.end_headers()
                 return None
@@ -718,10 +665,10 @@
         try:
             f = open(path, 'rb')
         except OSError:
-            self.send_error(404, "File not found")
+            self.send_error(HTTPStatus.NOT_FOUND, "File not found")
             return None
         try:
-            self.send_response(200)
+            self.send_response(HTTPStatus.OK)
             self.send_header("Content-type", ctype)
             fs = os.fstat(f.fileno())
             self.send_header("Content-Length", str(fs[6]))
@@ -743,7 +690,9 @@
         try:
             list = os.listdir(path)
         except OSError:
-            self.send_error(404, "No permission to list directory")
+            self.send_error(
+                HTTPStatus.NOT_FOUND,
+                "No permission to list directory")
             return None
         list.sort(key=lambda a: a.lower())
         r = []
@@ -782,7 +731,7 @@
         f = io.BytesIO()
         f.write(encoded)
         f.seek(0)
-        self.send_response(200)
+        self.send_response(HTTPStatus.OK)
         self.send_header("Content-type", "text/html; charset=%s" % enc)
         self.send_header("Content-Length", str(len(encoded)))
         self.end_headers()
@@ -964,7 +913,9 @@
         if self.is_cgi():
             self.run_cgi()
         else:
-            self.send_error(501, "Can only POST to CGI scripts")
+            self.send_error(
+                HTTPStatus.NOT_IMPLEMENTED,
+                "Can only POST to CGI scripts")
 
     def send_head(self):
         """Version of send_head that support CGI scripts"""
@@ -1042,17 +993,21 @@
         scriptname = dir + '/' + script
         scriptfile = self.translate_path(scriptname)
         if not os.path.exists(scriptfile):
-            self.send_error(404, "No such CGI script (%r)" % scriptname)
+            self.send_error(
+                HTTPStatus.NOT_FOUND,
+                "No such CGI script (%r)" % scriptname)
             return
         if not os.path.isfile(scriptfile):
-            self.send_error(403, "CGI script is not a plain file (%r)" %
-                            scriptname)
+            self.send_error(
+                HTTPStatus.FORBIDDEN,
+                "CGI script is not a plain file (%r)" % scriptname)
             return
         ispy = self.is_python(scriptname)
         if self.have_fork or not ispy:
             if not self.is_executable(scriptfile):
-                self.send_error(403, "CGI script is not executable (%r)" %
-                                scriptname)
+                self.send_error(
+                    HTTPStatus.FORBIDDEN,
+                    "CGI script is not executable (%r)" % scriptname)
                 return
 
         # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
@@ -1120,7 +1075,7 @@
                   'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
             env.setdefault(k, "")
 
-        self.send_response(200, "Script output follows")
+        self.send_response(HTTPStatus.OK, "Script output follows")
         self.flush_headers()
 
         decoded_query = query.replace('+', ' ')
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -196,6 +196,9 @@
 Library
 -------
 
+- Issue #21793: Added http.HTTPStatus enums (i.e. HTTPStatus.OK,
+  HTTPStatus.NOT_FOUND).  Patch by Demian Brecht.
+
 - Issue #23093: In the io, module allow more operations to work on detached
   streams.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list