Index: Lib/xmlrpclib.py =================================================================== --- Lib/xmlrpclib.py (revision 63197) +++ Lib/xmlrpclib.py (working copy) @@ -1205,6 +1205,8 @@ def __init__(self, use_datetime=0): self._use_datetime = use_datetime + self.__connection = None + self.__host = None ## # Send a complete request, and parse the response. @@ -1222,29 +1224,26 @@ if verbose: h.set_debuglevel(1) - self.send_request(h, handler, request_body) + self.send_request(h, handler) self.send_host(h, host) self.send_user_agent(h) + self.send_content(h, request_body) - errcode, errmsg, headers = h.getreply() + response = h.getresponse() - if errcode != 200: + if response.status != 200: raise ProtocolError( host + handler, - errcode, errmsg, - headers + response.status, + response.reason, + response.getheaders() ) self.verbose = verbose - try: - sock = h._conn.sock - except AttributeError: - sock = None + return self._parse_response(response.read()) - return self._parse_response(h.getfile(), sock) - ## # Create parser. # @@ -1292,10 +1291,29 @@ # @return A connection handle. def make_connection(self, host): + if self.__host != host: + self.__connection = None + elif self.__connection is not None: + import socket + try: + # If the connection is still alive, this will raise an + # exception, because it would block. If the remote side has + # shut down the connection, we get EOF instead, so need + # to reconnect. + self.__connection.sock.recv(1, + socket.MSG_PEEK | + socket.MSG_DONTWAIT) + self.__connection = None + except socket.error: + pass + # create a HTTP connection object from a host descriptor - import httplib - host, extra_headers, x509 = self.get_host_info(host) - return httplib.HTTP(host) + if self.__connection is None: + self.__host = host + import httplib + host, extra_headers, x509 = self.get_host_info(host) + self.__connection = httplib.HTTPConnection(host) + return self.__connection ## # Send request header. @@ -1304,8 +1322,8 @@ # @param handler Target RPC handler. # @param request_body XML-RPC body. - def send_request(self, connection, handler, request_body): - connection.putrequest("POST", handler) + def send_request(self, connection, handler): + connection.putrequest("POST", handler, skip_host=True) ## # Send host name. @@ -1339,49 +1357,27 @@ def send_content(self, connection, request_body): connection.putheader("Content-Type", "text/xml") connection.putheader("Content-Length", str(len(request_body))) - connection.endheaders() + connection.endheaders(flush=False) if request_body: connection.send(request_body) + else: + connection.flush() - ## - # Parse response. - # - # @param file Stream. - # @return Response tuple and target method. - def parse_response(self, file): - # compatibility interface - return self._parse_response(file, None) - ## - # Parse response (alternate interface). This is similar to the - # parse_response method, but also provides direct access to the - # underlying socket object (where available). # - # @param file Stream. - # @param sock Socket handle (or None, if the socket object - # could not be accessed). + # @param data Data to be handed to the parser. # @return Response tuple and target method. - def _parse_response(self, file, sock): - # read response from input file/socket, and parse it + def _parse_response(self, data): p, u = self.getparser() - while 1: - if sock: - response = sock.recv(1024) - else: - response = file.read(1024) - if not response: - break - if self.verbose: - print "body:", repr(response) - p.feed(response) + if self.verbose: + print "body:", repr(data) + p.feed(data) - file.close() p.close() - return u.close() ## Index: Lib/BaseHTTPServer.py =================================================================== --- Lib/BaseHTTPServer.py (revision 63197) +++ Lib/BaseHTTPServer.py (working copy) @@ -218,6 +218,22 @@ # where each string is of the form name[/version]. server_version = "BaseHTTP/" + __version__ + def __init__(self, request, client_address, parent): + self._buffer = None + self.__headers = False + socketserver.StreamRequestHandler.__init__(self, request, client_address, parent) + + def _output(self, data): + if self._buffer is None: + self._buffer = data + else: + self._buffer += data + + def flush(self): + if self._buffer is not None: + self.wfile.write(self._buffer) + self._buffer = None + def parse_request(self): """Parse a request (internal). @@ -348,7 +364,8 @@ self.send_header('Connection', 'close') self.end_headers() if self.command != 'HEAD' and code >= 200 and code not in (204, 304): - self.wfile.write(content) + self._output(content) + self.flush() error_message_format = DEFAULT_ERROR_MESSAGE error_content_type = DEFAULT_ERROR_CONTENT_TYPE @@ -367,8 +384,8 @@ else: message = '' if self.request_version != 'HTTP/0.9': - self.wfile.write("%s %d %s\r\n" % - (self.protocol_version, code, message)) + self._output("%s %d %s\r\n" % + (self.protocol_version, code, message)) # print (self.protocol_version, code, message) self.send_header('Server', self.version_string()) self.send_header('Date', self.date_time_string()) @@ -376,7 +393,7 @@ def send_header(self, keyword, value): """Send a MIME header.""" if self.request_version != 'HTTP/0.9': - self.wfile.write("%s: %s\r\n" % (keyword, value)) + self._output("%s: %s\r\n" % (keyword, value)) if keyword.lower() == 'connection': if value.lower() == 'close': @@ -387,8 +404,15 @@ def end_headers(self): """Send the blank line ending the MIME headers.""" if self.request_version != 'HTTP/0.9': - self.wfile.write("\r\n") + self._output("\r\n") + self.__headers = True + def send_body(self, data): + if not self.__headers: + self.end_headers() + self._output(data) + self.flush() + def log_request(self, code='-', size='-'): """Log an accepted request. @@ -479,7 +503,7 @@ # The version of the HTTP protocol we support. # Set this to HTTP/1.1 to enable automatic keepalive - protocol_version = "HTTP/1.0" + protocol_version = "HTTP/1.1" # The Message-like class used to parse headers MessageClass = mimetools.Message Index: Lib/httplib.py =================================================================== --- Lib/httplib.py (revision 63197) +++ Lib/httplib.py (working copy) @@ -463,7 +463,6 @@ if self.version == 11: # An HTTP/1.1 proxy is assumed to stay open unless # explicitly closed. - conn = self.msg.getheader('connection') if conn and "close" in conn.lower(): return True return False @@ -671,6 +670,9 @@ def set_debuglevel(self, level): self.debuglevel = level + def flush(self): + self._send_output() + def connect(self): """Connect to the host and port specified in __init__.""" self.sock = socket.create_connection((self.host,self.port), @@ -687,7 +689,13 @@ self.__state = _CS_IDLE def send(self, str): - """Send `str' to the server.""" + self._output(str) + self._send_output() + + def _send(self, str): + """Send `str' to the server. + Implies flushing any buffered data first.""" + if self.sock is None: if self.auto_open: self.connect() @@ -724,15 +732,12 @@ self._buffer.append(s) def _send_output(self): - """Send the currently buffered request and clear the buffer. - - Appends an extra \\r\\n to the buffer. - """ - self._buffer.extend(("", "")) - msg = "\r\n".join(self._buffer) - del self._buffer[:] - self.send(msg) - + """Send the currently buffered request and clear the buffer.""" + if self._buffer: + msg = "\r\n".join(self._buffer) + del self._buffer[:] + self._send(msg) + def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): """Send a request to the server. @@ -851,15 +856,17 @@ str = '%s: %s' % (header, value) self._output(str) - def endheaders(self): + def endheaders(self, flush=True): """Indicate that the last header line has been sent to the server.""" + self._output('') if self.__state == _CS_REQ_STARTED: self.__state = _CS_REQ_SENT else: raise CannotSendHeader() - self._send_output() + if flush: + self._send_output() def request(self, method, url, body=None, headers={}): """Send a complete request to the server.""" @@ -905,8 +912,10 @@ self.endheaders() if body: - self.send(body) + self._output(body) + self._send_output() + def getresponse(self): "Get the response from the server." Index: Lib/SimpleXMLRPCServer.py =================================================================== --- Lib/SimpleXMLRPCServer.py (revision 63197) +++ Lib/SimpleXMLRPCServer.py (working copy) @@ -342,8 +342,7 @@ return pydoc.getdoc(method) def system_multicall(self, call_list): - """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ -[[4], ...] + """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => [[4], ...] Allows the caller to package multiple XML-RPC calls into a single request. @@ -487,12 +486,25 @@ self.send_response(200) self.send_header("Content-type", "text/xml") self.send_header("Content-length", str(len(response))) + if self.request_version == 'HTTP/1.1': + # default for 1.1 is keepalive, default for others is close + self.close_connection = False + if (self.headers.has_key("connection") + and self.headers["connection"].find("close") != -1): + self.close_connection = True + else: + self.close_connection = True + if (self.headers.has_key("connection") + and self.headers["connection"].lower().find("keep-alive") != -1): + self.close_connection = False + self.send_header("Connection", "Keep-Alive") self.end_headers() - self.wfile.write(response) + self.send_body(response) - # shut down the connection - self.wfile.flush() - self.connection.shutdown(1) + # shut down the connection, if applicable: + self.log_message("Connection close: %r" % self.close_connection) + if self.close_connection: + self.connection.shutdown(1) def report_404 (self): # Report a 404 error @@ -500,10 +512,10 @@ response = 'No such page' self.send_header("Content-type", "text/plain") self.send_header("Content-length", str(len(response))) + self.send_header("Connection", "close") self.end_headers() - self.wfile.write(response) + self.send_body(response) # shut down the connection - self.wfile.flush() self.connection.shutdown(1) def log_request(self, code='-', size='-'):