[Python-checkins] r87340 - in python/branches/py3k: Doc/library/http.client.rst Lib/http/client.py Lib/test/test_urllib.py Misc/NEWS

antoine.pitrou python-checkins at python.org
Fri Dec 17 18:35:56 CET 2010


Author: antoine.pitrou
Date: Fri Dec 17 18:35:56 2010
New Revision: 87340

Log:
Issue #10711: Remove HTTP 0.9 support from http.client.  The `strict`
parameter to HTTPConnection and friends is deprecated.



Modified:
   python/branches/py3k/Doc/library/http.client.rst
   python/branches/py3k/Lib/http/client.py
   python/branches/py3k/Lib/test/test_urllib.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Doc/library/http.client.rst
==============================================================================
--- python/branches/py3k/Doc/library/http.client.rst	(original)
+++ python/branches/py3k/Doc/library/http.client.rst	Fri Dec 17 18:35:56 2010
@@ -23,16 +23,13 @@
 The module provides the following classes:
 
 
-.. class:: HTTPConnection(host, port=None, strict=None[, timeout[, source_address]])
+.. class:: HTTPConnection(host, port=None[, strict[, timeout[, source_address]]])
 
    An :class:`HTTPConnection` instance represents one transaction with an HTTP
    server.  It should be instantiated passing it a host and optional port
    number.  If no port number is passed, the port is extracted from the host
    string if it has the form ``host:port``, else the default HTTP port (80) is
-   used.  When True, the optional parameter *strict* (which defaults to a false
-   value) causes ``BadStatusLine`` to
-   be raised if the status line can't be parsed as a valid HTTP/1.0 or 1.1
-   status line.  If the optional *timeout* parameter is given, blocking
+   used.  If the optional *timeout* parameter is given, blocking
    operations (like connection attempts) will timeout after that many seconds
    (if it is not given, the global default timeout setting is used).
    The optional *source_address* parameter may be a typle of a (host, port)
@@ -49,8 +46,12 @@
    .. versionchanged:: 3.2
       *source_address* was added.
 
+   .. versionchanged:: 3.2
+      The *strict* parameter is deprecated.  HTTP 0.9-style "Simple Responses"
+      are not supported anymore.
+
 
-.. class:: HTTPSConnection(host, port=None, key_file=None, cert_file=None, strict=None[, timeout[, source_address]], *, context=None, check_hostname=None)
+.. class:: HTTPSConnection(host, port=None, key_file=None, cert_file=None[, strict[, timeout[, source_address]]], *, context=None, check_hostname=None)
 
    A subclass of :class:`HTTPConnection` that uses SSL for communication with
    secure servers.  Default port is ``443``.  If *context* is specified, it
@@ -80,12 +81,20 @@
       This class now supports HTTPS virtual hosts if possible (that is,
       if :data:`ssl.HAS_SNI` is true).
 
+   .. versionchanged:: 3.2
+      The *strict* parameter is deprecated.  HTTP 0.9-style "Simple Responses"
+      are not supported anymore.
+
 
-.. class:: HTTPResponse(sock, debuglevel=0, strict=0, method=None, url=None)
+.. class:: HTTPResponse(sock, debuglevel=0[, strict], method=None, url=None)
 
    Class whose instances are returned upon successful connection.  Not
    instantiated directly by user.
 
+   .. versionchanged:: 3.2
+      The *strict* parameter is deprecated.  HTTP 0.9-style "Simple Responses"
+      are not supported anymore.
+
 
 The following exceptions are raised as appropriate:
 

Modified: python/branches/py3k/Lib/http/client.py
==============================================================================
--- python/branches/py3k/Lib/http/client.py	(original)
+++ python/branches/py3k/Lib/http/client.py	Fri Dec 17 18:35:56 2010
@@ -252,13 +252,10 @@
     hstring = b''.join(headers).decode('iso-8859-1')
     return email.parser.Parser(_class=_class).parsestr(hstring)
 
-class HTTPResponse(io.RawIOBase):
 
-    # strict: If true, raise BadStatusLine if the status line can't be
-    # parsed as a valid HTTP/1.0 or 1.1 status line.  By default it is
-    # false because it prevents clients from talking to HTTP/0.9
-    # servers.  Note that a response with a sufficiently corrupted
-    # status line will look like an HTTP/0.9 response.
+_strict_sentinel = object()
+
+class HTTPResponse(io.RawIOBase):
 
     # See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.
 
@@ -267,7 +264,7 @@
     # text following RFC 2047.  The basic status line parsing only
     # accepts iso-8859-1.
 
-    def __init__(self, sock, debuglevel=0, strict=0, method=None, url=None):
+    def __init__(self, sock, debuglevel=0, strict=_strict_sentinel, method=None, url=None):
         # If the response includes a content-length header, we need to
         # make sure that the client doesn't read more than the
         # specified number of bytes.  If it does, it will block until
@@ -277,7 +274,10 @@
         # clients unless they know what they are doing.
         self.fp = sock.makefile("rb")
         self.debuglevel = debuglevel
-        self.strict = strict
+        if strict is not _strict_sentinel:
+            warnings.warn("the 'strict' argument isn't supported anymore; "
+                "http.client now always assumes HTTP/1.x compliant servers.",
+                DeprecationWarning, 2)
         self._method = method
 
         # The HTTPResponse object is returned via urllib.  The clients
@@ -299,7 +299,6 @@
         self.will_close = _UNKNOWN      # conn will close at end of response
 
     def _read_status(self):
-        # Initialize with Simple-Response defaults.
         line = str(self.fp.readline(), "iso-8859-1")
         if self.debuglevel > 0:
             print("reply:", repr(line))
@@ -308,25 +307,17 @@
             # sending a valid response.
             raise BadStatusLine(line)
         try:
-            [version, status, reason] = line.split(None, 2)
+            version, status, reason = line.split(None, 2)
         except ValueError:
             try:
-                [version, status] = line.split(None, 1)
+                version, status = line.split(None, 1)
                 reason = ""
             except ValueError:
-                # empty version will cause next test to fail and status
-                # will be treated as 0.9 response.
+                # empty version will cause next test to fail.
                 version = ""
         if not version.startswith("HTTP/"):
-            if self.strict:
-                self.close()
-                raise BadStatusLine(line)
-            else:
-                # Assume it's a Simple-Response from an 0.9 server.
-                # We have to convert the first line back to raw bytes
-                # because self.fp.readline() needs to return bytes.
-                self.fp = LineAndFileWrapper(bytes(line, "ascii"), self.fp)
-                return "HTTP/0.9", 200, ""
+            self.close()
+            raise BadStatusLine(line)
 
         # The status code is a three-digit number
         try:
@@ -357,22 +348,14 @@
 
         self.code = self.status = status
         self.reason = reason.strip()
-        if version == "HTTP/1.0":
+        if version in ("HTTP/1.0", "HTTP/0.9"):
+            # Some servers might still return "0.9", treat it as 1.0 anyway
             self.version = 10
         elif version.startswith("HTTP/1."):
             self.version = 11   # use HTTP/1.1 code for HTTP/1.x where x>=1
-        elif version == "HTTP/0.9":
-            self.version = 9
         else:
             raise UnknownProtocol(version)
 
-        if self.version == 9:
-            self.length = None
-            self.chunked = False
-            self.will_close = True
-            self.headers = self.msg = email.message_from_string('')
-            return
-
         self.headers = self.msg = parse_headers(self.fp)
 
         if self.debuglevel > 0:
@@ -639,10 +622,13 @@
     default_port = HTTP_PORT
     auto_open = 1
     debuglevel = 0
-    strict = 0
 
-    def __init__(self, host, port=None, strict=None,
+    def __init__(self, host, port=None, strict=_strict_sentinel,
                  timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None):
+        if strict is not _strict_sentinel:
+            warnings.warn("the 'strict' argument isn't supported anymore; "
+                "http.client now always assumes HTTP/1.x compliant servers.",
+                DeprecationWarning, 2)
         self.timeout = timeout
         self.source_address = source_address
         self.sock = None
@@ -654,8 +640,6 @@
         self._tunnel_port = None
 
         self._set_hostport(host, port)
-        if strict is not None:
-            self.strict = strict
 
     def set_tunnel(self, host, port=None, headers=None):
         """ Sets up the host and the port for the HTTP CONNECT Tunnelling.
@@ -700,8 +684,7 @@
             header_bytes = header_str.encode("ascii")
             self.send(header_bytes)
 
-        response = self.response_class(self.sock, strict = self.strict,
-                                       method = self._method)
+        response = self.response_class(self.sock, method = self._method)
         (version, code, message) = response._read_status()
 
         if code != 200:
@@ -1025,11 +1008,9 @@
 
         if self.debuglevel > 0:
             response = self.response_class(self.sock, self.debuglevel,
-                                           strict=self.strict,
                                            method=self._method)
         else:
-            response = self.response_class(self.sock, strict=self.strict,
-                                           method=self._method)
+            response = self.response_class(self.sock, method=self._method)
 
         response.begin()
         assert response.will_close != _UNKNOWN
@@ -1057,7 +1038,7 @@
         # XXX Should key_file and cert_file be deprecated in favour of context?
 
         def __init__(self, host, port=None, key_file=None, cert_file=None,
-                     strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+                     strict=_strict_sentinel, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                      source_address=None, *, context=None, check_hostname=None):
             super(HTTPSConnection, self).__init__(host, port, strict, timeout,
                                                   source_address)
@@ -1158,71 +1139,3 @@
 
 # for backwards compatibility
 error = HTTPException
-
-class LineAndFileWrapper:
-    """A limited file-like object for HTTP/0.9 responses."""
-
-    # The status-line parsing code calls readline(), which normally
-    # get the HTTP status line.  For a 0.9 response, however, this is
-    # actually the first line of the body!  Clients need to get a
-    # readable file object that contains that line.
-
-    def __init__(self, line, file):
-        self._line = line
-        self._file = file
-        self._line_consumed = 0
-        self._line_offset = 0
-        self._line_left = len(line)
-
-    def __getattr__(self, attr):
-        return getattr(self._file, attr)
-
-    def _done(self):
-        # called when the last byte is read from the line.  After the
-        # call, all read methods are delegated to the underlying file
-        # object.
-        self._line_consumed = 1
-        self.read = self._file.read
-        self.readline = self._file.readline
-        self.readlines = self._file.readlines
-
-    def read(self, amt=None):
-        if self._line_consumed:
-            return self._file.read(amt)
-        assert self._line_left
-        if amt is None or amt > self._line_left:
-            s = self._line[self._line_offset:]
-            self._done()
-            if amt is None:
-                return s + self._file.read()
-            else:
-                return s + self._file.read(amt - len(s))
-        else:
-            assert amt <= self._line_left
-            i = self._line_offset
-            j = i + amt
-            s = self._line[i:j]
-            self._line_offset = j
-            self._line_left -= amt
-            if self._line_left == 0:
-                self._done()
-            return s
-
-    def readline(self):
-        if self._line_consumed:
-            return self._file.readline()
-        assert self._line_left
-        s = self._line[self._line_offset:]
-        self._done()
-        return s
-
-    def readlines(self, size=None):
-        if self._line_consumed:
-            return self._file.readlines(size)
-        assert self._line_left
-        L = [self._line[self._line_offset:]]
-        self._done()
-        if size is None:
-            return L + self._file.readlines()
-        else:
-            return L + self._file.readlines(size)

Modified: python/branches/py3k/Lib/test/test_urllib.py
==============================================================================
--- python/branches/py3k/Lib/test/test_urllib.py	(original)
+++ python/branches/py3k/Lib/test/test_urllib.py	Fri Dec 17 18:35:56 2010
@@ -139,8 +139,10 @@
 
     def fakehttp(self, fakedata):
         class FakeSocket(io.BytesIO):
+            io_refs = 1
             def sendall(self, str): pass
             def makefile(self, *args, **kwds):
+                self.io_refs += 1
                 return self
             def read(self, amt=None):
                 if self.closed: return b""
@@ -148,6 +150,10 @@
             def readline(self, length=None):
                 if self.closed: return b""
                 return io.BytesIO.readline(self, length)
+            def close(self):
+                self.io_refs -= 1
+                if self.io_refs == 0:
+                    io.BytesIO.close(self)
         class FakeHTTPConnection(http.client.HTTPConnection):
             def connect(self):
                 self.sock = FakeSocket(fakedata)
@@ -157,8 +163,8 @@
     def unfakehttp(self):
         http.client.HTTPConnection = self._connection_class
 
-    def test_read(self):
-        self.fakehttp(b"Hello!")
+    def check_read(self, ver):
+        self.fakehttp(b"HTTP/" + ver + b" 200 OK\r\n\r\nHello!")
         try:
             fp = urlopen("http://python.org/")
             self.assertEqual(fp.readline(), b"Hello!")
@@ -168,6 +174,17 @@
         finally:
             self.unfakehttp()
 
+    def test_read_0_9(self):
+        # "0.9" response accepted (but not "simple responses" without
+        # a status line)
+        self.check_read(b"0.9")
+
+    def test_read_1_0(self):
+        self.check_read(b"1.0")
+
+    def test_read_1_1(self):
+        self.check_read(b"1.1")
+
     def test_read_bogus(self):
         # urlopen() should raise IOError for many error codes.
         self.fakehttp(b'''HTTP/1.1 401 Authentication Required
@@ -191,7 +208,7 @@
             self.unfakehttp()
 
     def test_userpass_inurl(self):
-        self.fakehttp(b"Hello!")
+        self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!")
         try:
             fp = urlopen("http://user:pass@python.org/")
             self.assertEqual(fp.readline(), b"Hello!")

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Fri Dec 17 18:35:56 2010
@@ -19,6 +19,10 @@
 
 Library
 -------
+
+- Issue #10711: Remove HTTP 0.9 support from http.client.  The ``strict``
+  parameter to HTTPConnection and friends is deprecated.
+
 - Issue #9721: Fix the behavior of urljoin when the relative url starts with a
   ';' character. Patch by Wes Chow.
 


More information about the Python-checkins mailing list