Merged revisions 77263-77264 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r77263 | gregory.p.smith | 2010-01-02 17:29:44 -0800 (Sat, 02 Jan 2010) | 4 lines

  Adds an optional source_address parameter to socket.create_connection().

  For use by issue3972.
........
  r77264 | gregory.p.smith | 2010-01-02 18:06:07 -0800 (Sat, 02 Jan 2010) | 5 lines

  issue3972: HTTPConnection and HTTPSConnection now support a
  source_address parameter.

  Also cleans up an annotation in the socket documentation.
........
This commit is contained in:
Gregory P. Smith 2010-01-03 03:28:29 +00:00
parent 91ae4a1404
commit b4066374db
7 changed files with 89 additions and 14 deletions

View File

@ -23,7 +23,7 @@ HTTPS protocols. It is normally not used directly --- the module
The module provides the following classes: The module provides the following classes:
.. class:: HTTPConnection(host, port=None, strict=None[, timeout]) .. class:: HTTPConnection(host, port=None, strict=None[, timeout[, source_address]])
An :class:`HTTPConnection` instance represents one transaction with an HTTP An :class:`HTTPConnection` instance represents one transaction with an HTTP
server. It should be instantiated passing it a host and optional port server. It should be instantiated passing it a host and optional port
@ -35,6 +35,8 @@ The module provides the following classes:
status line. If the optional *timeout* parameter is given, blocking status line. If the optional *timeout* parameter is given, blocking
operations (like connection attempts) will timeout after that many seconds operations (like connection attempts) will timeout after that many seconds
(if it is not given, the global default timeout setting is used). (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)
to use as the source address the HTTP connection is made from.
For example, the following calls all create instances that connect to the server For example, the following calls all create instances that connect to the server
at the same host and port:: at the same host and port::
@ -44,8 +46,11 @@ The module provides the following classes:
>>> h3 = http.client.HTTPConnection('www.cwi.nl', 80) >>> h3 = http.client.HTTPConnection('www.cwi.nl', 80)
>>> h3 = http.client.HTTPConnection('www.cwi.nl', 80, timeout=10) >>> h3 = http.client.HTTPConnection('www.cwi.nl', 80, timeout=10)
.. versionchanged:: 3.2
*source_address* was added.
.. class:: HTTPSConnection(host, port=None, key_file=None, cert_file=None, strict=None[, timeout])
.. class:: HTTPSConnection(host, port=None, key_file=None, cert_file=None, strict=None[, timeout[, source_address]])
A subclass of :class:`HTTPConnection` that uses SSL for communication with A subclass of :class:`HTTPConnection` that uses SSL for communication with
secure servers. Default port is ``443``. *key_file* is the name of a PEM secure servers. Default port is ``443``. *key_file* is the name of a PEM
@ -56,6 +61,9 @@ The module provides the following classes:
This does not do any certificate verification. This does not do any certificate verification.
.. versionchanged:: 3.2
*source_address* was added.
.. class:: HTTPResponse(sock, debuglevel=0, strict=0, method=None, url=None) .. class:: HTTPResponse(sock, debuglevel=0, strict=0, method=None, url=None)

View File

@ -194,7 +194,7 @@ The module :mod:`socket` exports the following constants and functions:
this platform. this platform.
.. function:: create_connection(address[, timeout]) .. function:: create_connection(address[, timeout[, source_address]])
Convenience function. Connect to *address* (a 2-tuple ``(host, port)``), Convenience function. Connect to *address* (a 2-tuple ``(host, port)``),
and return the socket object. Passing the optional *timeout* parameter will and return the socket object. Passing the optional *timeout* parameter will
@ -202,6 +202,13 @@ The module :mod:`socket` exports the following constants and functions:
*timeout* is supplied, the global default timeout setting returned by *timeout* is supplied, the global default timeout setting returned by
:func:`getdefaulttimeout` is used. :func:`getdefaulttimeout` is used.
If supplied, *source_address* must be a 2-tuple ``(host, port)`` for the
socket to bind to as its source address before connecting. If host or port
are '' or 0 respectively the OS default behavior will be used.
.. versionchanged:: 3.2
*source_address* was added.
.. function:: getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]]) .. function:: getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]])

View File

@ -634,8 +634,9 @@ class HTTPConnection:
strict = 0 strict = 0
def __init__(self, host, port=None, strict=None, def __init__(self, host, port=None, strict=None,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT): timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None):
self.timeout = timeout self.timeout = timeout
self.source_address = source_address
self.sock = None self.sock = None
self._buffer = [] self._buffer = []
self.__response = None self.__response = None
@ -707,7 +708,7 @@ class HTTPConnection:
def connect(self): def connect(self):
"""Connect to the host and port specified in __init__.""" """Connect to the host and port specified in __init__."""
self.sock = socket.create_connection((self.host,self.port), self.sock = socket.create_connection((self.host,self.port),
self.timeout) self.timeout, self.source_address)
if self._tunnel_host: if self._tunnel_host:
self._tunnel() self._tunnel()
@ -1042,8 +1043,10 @@ else:
default_port = HTTPS_PORT default_port = HTTPS_PORT
def __init__(self, host, port=None, key_file=None, cert_file=None, def __init__(self, host, port=None, key_file=None, cert_file=None,
strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
HTTPConnection.__init__(self, host, port, strict, timeout) source_address=None):
super(HTTPSConnection, self).__init__(host, port, strict, timeout,
source_address)
self.key_file = key_file self.key_file = key_file
self.cert_file = cert_file self.cert_file = cert_file
@ -1051,7 +1054,7 @@ else:
"Connect to a host on a given (SSL) port." "Connect to a host on a given (SSL) port."
sock = socket.create_connection((self.host, self.port), sock = socket.create_connection((self.host, self.port),
self.timeout) self.timeout, self.source_address)
if self._tunnel_host: if self._tunnel_host:
self.sock = sock self.sock = sock

View File

@ -23,7 +23,8 @@ inet_aton() -- convert IP addr string (123.45.67.89) to 32-bit packed format
inet_ntoa() -- convert 32-bit packed format IP to string (123.45.67.89) inet_ntoa() -- convert 32-bit packed format IP to string (123.45.67.89)
socket.getdefaulttimeout() -- get the default timeout value socket.getdefaulttimeout() -- get the default timeout value
socket.setdefaulttimeout() -- set the default timeout value socket.setdefaulttimeout() -- set the default timeout value
create_connection() -- connects to an address, with an optional timeout create_connection() -- connects to an address, with an optional timeout and
optional source address.
[*] not available on all platforms! [*] not available on all platforms!
@ -276,7 +277,8 @@ def getfqdn(name=''):
_GLOBAL_DEFAULT_TIMEOUT = object() _GLOBAL_DEFAULT_TIMEOUT = object()
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT): def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
"""Connect to *address* and return the socket object. """Connect to *address* and return the socket object.
Convenience function. Connect to *address* (a 2-tuple ``(host, Convenience function. Connect to *address* (a 2-tuple ``(host,
@ -284,7 +286,9 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
*timeout* parameter will set the timeout on the socket instance *timeout* parameter will set the timeout on the socket instance
before attempting to connect. If no *timeout* is supplied, the before attempting to connect. If no *timeout* is supplied, the
global default timeout setting returned by :func:`getdefaulttimeout` global default timeout setting returned by :func:`getdefaulttimeout`
is used. is used. If *source_address* is set it must be a tuple of (host, port)
for the socket to bind as a source address before making the connection.
An host of '' or port 0 tells the OS to use the default.
""" """
msg = "getaddrinfo returns an empty list" msg = "getaddrinfo returns an empty list"
@ -296,6 +300,8 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
sock = socket(af, socktype, proto) sock = socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT: if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout) sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa) sock.connect(sa)
return sock return sock

View File

@ -4,7 +4,8 @@ import io
import array import array
import socket import socket
from unittest import TestCase import unittest
TestCase = unittest.TestCase
from test import support from test import support
@ -263,6 +264,38 @@ class OfflineTest(TestCase):
def test_responses(self): def test_responses(self):
self.assertEquals(client.responses[client.NOT_FOUND], "Not Found") self.assertEquals(client.responses[client.NOT_FOUND], "Not Found")
class SourceAddressTest(TestCase):
def setUp(self):
self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.port = support.bind_port(self.serv)
self.source_port = support.find_unused_port()
self.serv.listen(5)
self.conn = None
def tearDown(self):
if self.conn:
self.conn.close()
self.conn = None
self.serv.close()
self.serv = None
def testHTTPConnectionSourceAddress(self):
self.conn = client.HTTPConnection(HOST, self.port,
source_address=('', self.source_port))
self.conn.connect()
self.assertEqual(self.conn.sock.getsockname()[1], self.source_port)
@unittest.skipIf(not hasattr(client, 'HTTPSConnection'),
'http.client.HTTPSConnection not defined')
def testHTTPSConnectionSourceAddress(self):
self.conn = client.HTTPSConnection(HOST, self.port,
source_address=('', self.source_port))
# We don't test anything here other the constructor not barfing as
# this code doesn't deal with setting up an active running SSL server
# for an ssl_wrapped connect() to actually return from.
class TimeoutTest(TestCase): class TimeoutTest(TestCase):
PORT = None PORT = None
@ -390,7 +423,7 @@ class RequestBodyTest(TestCase):
def test_main(verbose=None): def test_main(verbose=None):
support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest, support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest,
HTTPSTimeoutTest, RequestBodyTest) HTTPSTimeoutTest, RequestBodyTest, SourceAddressTest)
if __name__ == '__main__': if __name__ == '__main__':
test_main() test_main()

View File

@ -993,7 +993,7 @@ class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):
ThreadableTest.__init__(self) ThreadableTest.__init__(self)
def clientSetUp(self): def clientSetUp(self):
pass self.source_port = support.find_unused_port()
def clientTearDown(self): def clientTearDown(self):
self.cli.close() self.cli.close()
@ -1008,6 +1008,19 @@ class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):
self.cli = socket.create_connection((HOST, self.port), timeout=30) self.cli = socket.create_connection((HOST, self.port), timeout=30)
self.assertEqual(self.cli.family, 2) self.assertEqual(self.cli.family, 2)
testSourcePort = _justAccept
def _testSourcePort(self):
self.cli = socket.create_connection((HOST, self.port), timeout=30,
source_address=('', self.source_port))
self.assertEqual(self.cli.getsockname()[1], self.source_port)
testSourceAddress = _justAccept
def _testSourceAddress(self):
self.cli = socket.create_connection(
(HOST, self.port), 30, ('127.0.0.1', self.source_port))
self.assertEqual(self.cli.getsockname(),
('127.0.0.1', self.source_port))
testTimeoutDefault = _justAccept testTimeoutDefault = _justAccept
def _testTimeoutDefault(self): def _testTimeoutDefault(self):
# passing no explicit timeout uses socket's global default # passing no explicit timeout uses socket's global default

View File

@ -191,6 +191,11 @@ C-API
Library Library
------- -------
_ Issue #3972: http.client.HTTPConnection now accepts an optional source_address
parameter to allow specifying where your connections come from.
- socket.create_connection now accepts an optional source_address parameter.
- Issue #5511: now zipfile.ZipFile can be used as a context manager. - Issue #5511: now zipfile.ZipFile can be used as a context manager.
Initial patch by Brian Curtin. Initial patch by Brian Curtin.