[Python-checkins] r67442 - in python/trunk/Lib: httplib.py xmlrpclib.py

jeremy.hylton python-checkins at python.org
Sat Nov 29 02:09:35 CET 2008


Author: jeremy.hylton
Date: Sat Nov 29 02:09:35 2008
New Revision: 67442

Log:
Send HTTP headers and message body in a single send() call.

This change addresses part of issue 4336.  

Change endheaders() to take an optional message_body argument
that is sent along with the headers.  Change xmlrpclib and
httplib's other methods to use this new interface.

It is more efficient to make a single send() call, which should
get the entire client request into one packet (assuming it is
smaller than the MTU) and will avoid the long pause for delayed
ack following timeout.

Also:
- Add a comment about the buffer size for makefile().
- Extract _set_content_length() method and fix whitespace issues there.



Modified:
   python/trunk/Lib/httplib.py
   python/trunk/Lib/xmlrpclib.py

Modified: python/trunk/Lib/httplib.py
==============================================================================
--- python/trunk/Lib/httplib.py	(original)
+++ python/trunk/Lib/httplib.py	Sat Nov 29 02:09:35 2008
@@ -326,6 +326,11 @@
     # See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.
 
     def __init__(self, sock, debuglevel=0, strict=0, method=None):
+        # The buffer size is specified as zero, because the headers of
+        # the response are read with readline().  If the reads were
+        # buffered the readline() calls could consume some of the
+        # response, which make be read via a recv() on the underlying
+        # socket.
         self.fp = sock.makefile('rb', 0)
         self.debuglevel = debuglevel
         self.strict = strict
@@ -729,7 +734,7 @@
         """
         self._buffer.append(s)
 
-    def _send_output(self):
+    def _send_output(self, message_body=None):
         """Send the currently buffered request and clear the buffer.
 
         Appends an extra \\r\\n to the buffer.
@@ -737,6 +742,11 @@
         self._buffer.extend(("", ""))
         msg = "\r\n".join(self._buffer)
         del self._buffer[:]
+        # If msg and message_body are sent in a single send() call,
+        # it will avoid performance problems caused by the interaction
+        # between delayed ack and the Nagle algorithim.
+        if message_body is not None:
+            msg += message_body
         self.send(msg)
 
     def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
@@ -857,15 +867,20 @@
         str = '%s: %s' % (header, '\r\n\t'.join(values))
         self._output(str)
 
-    def endheaders(self):
-        """Indicate that the last header line has been sent to the server."""
+    def endheaders(self, message_body=None):
+        """Indicate that the last header line has been sent to the server.
 
+        This method sends the request to the server.  The optional
+        message_body argument can be used to pass message body
+        associated with the request.  The message body will be sent in
+        the same packet as the message headers if possible.  The
+        message_body should be a string.
+        """
         if self.__state == _CS_REQ_STARTED:
             self.__state = _CS_REQ_SENT
         else:
             raise CannotSendHeader()
-
-        self._send_output()
+        self._send_output(message_body)
 
     def request(self, method, url, body=None, headers={}):
         """Send a complete request to the server."""
@@ -879,6 +894,24 @@
             # try one more time
             self._send_request(method, url, body, headers)
 
+    def _set_content_length(self, body):
+        # Set the content-length based on the body.
+        thelen = None
+        try:
+            thelen = str(len(body))
+        except TypeError, te:
+            # If this is a file-like object, try to
+            # fstat its file descriptor
+            import os
+            try:
+                thelen = str(os.fstat(body.fileno()).st_size)
+            except (AttributeError, OSError):
+                # Don't send a length if this failed
+                if self.debuglevel > 0: print "Cannot stat!!"
+
+        if thelen is not None:
+            self.putheader('Content-Length', thelen)
+
     def _send_request(self, method, url, body, headers):
         # honour explicitly requested Host: and Accept-Encoding headers
         header_names = dict.fromkeys([k.lower() for k in headers])
@@ -891,27 +924,15 @@
         self.putrequest(method, url, **skips)
 
         if body and ('content-length' not in header_names):
-            thelen=None
-            try:
-                thelen=str(len(body))
-            except TypeError, te:
-                # If this is a file-like object, try to
-                # fstat its file descriptor
-                import os
-                try:
-                    thelen = str(os.fstat(body.fileno()).st_size)
-                except (AttributeError, OSError):
-                    # Don't send a length if this failed
-                    if self.debuglevel > 0: print "Cannot stat!!"
-
-            if thelen is not None:
-                self.putheader('Content-Length',thelen)
+            self._set_content_length(body)
         for hdr, value in headers.iteritems():
             self.putheader(hdr, value)
-        self.endheaders()
-
-        if body:
-            self.send(body)
+        if isinstance(body, str):
+            self.endheaders(body)
+        else:
+            self.endheaders()
+            if body:  # when body is a file rather than a string
+                self.send(body)
 
     def getresponse(self):
         "Get the response from the server."

Modified: python/trunk/Lib/xmlrpclib.py
==============================================================================
--- python/trunk/Lib/xmlrpclib.py	(original)
+++ python/trunk/Lib/xmlrpclib.py	Sat Nov 29 02:09:35 2008
@@ -1346,9 +1346,7 @@
     def send_content(self, connection, request_body):
         connection.putheader("Content-Type", "text/xml")
         connection.putheader("Content-Length", str(len(request_body)))
-        connection.endheaders()
-        if request_body:
-            connection.send(request_body)
+        connection.endheaders(request_body)
 
     ##
     # Parse response.


More information about the Python-checkins mailing list