urllib3 has been updated to new version 1.8.1, "source_address" is supported. May the requests lib support it?

New version urllib3 has been updated to 1.8.1 which support the "source_address"(in Python v2.7). Could you do something change to support it. It's really useful and needed. I will appreciate it very much.


Thanks for raising this issue!

Requests doesn't plan to add this to the main API, it's simply not commonly used enough to justify the increased complexity. My recommendation is that you use our [Transport Adapter abstraction](http://docs.python-requests.org/en/latest/user/advanced/#transport-adapters) to provide the value. The example adapter that I linked to should provide enough of an example to demonstrate how this would work, but let me know if it didn't and I'll demonstrate.


@Lukasa Thanks for your reply. But, what I want is to send http requests with specifying source address. I've no idea how to use the  Transport Adapter abstraction to finish this. Maybe you can give an example how it does. 


Doing it at the per-request level is very difficult, but you can do it at either a global level or a per-host level.

For example:

``` python
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager


class SourceAddressAdapter(HTTPAdapter):
    def __init__(self, source_address, **kwargs):
        self.source_address = source_address

        super(SourceAddressAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       source_address=self.source_address)

s = requests.Session()
s.mount('http://', SourceAddressAdapter('10.10.10.10'))
s.mount('https://', SourceAddressAdapter('12.12.12.12'))
s.mount('http://github.com/', SourceAddressAdapter('120.120.120.120'))
```

This code will set the source address to 10.10.10.10 for all HTTP traffic, and 12.12.12.12 for all HTTPS traffic except when sending to GitHub, when it'll use 120.120.120.120. Is this suitably useful?


@Lukasa Great! That's what I want. After replacing with the new version urllib3, I did it! I hope there will be new version requests which embedded with new urllib3 as soon as possible. 
It's so kind of you to show me this! Thanks you again. 


No problem, I'll make sure we update urllib3 before our next release. =)


Should we move the SourceAddressAdapter into the toolbelt?


Let's do it. =)


PRs welcome. :)

Thinking we should have an adapters submodule for it. ;)


@Lukasa 

thanks for the above dode
your code above have a small issue.

```
SourceAddressAdapter('10.10.10.10'))
```

the source_address should be a pair (host, port).

You pass a string and this will cause an exception when the sock.bind method is called.

The correct way is to pass a tuple like:

```
SourceAddressAdapter( ('10.10.10.10', 0) )
```

cheers


@gosom You're quite right: the version of the code in requests-toolbelt does not have this problem.


@Lukasa 
I think the version of the code should not work with the current requrests version.

check here:https://github.com/kennethreitz/requests/blob/master/requests/packages/urllib3/util/connection.py#L77

The socket.bind method it is called and you get an exception if you pass a string.

Ok, this is not a bug but would be nice to be documented that source_address 
should be a (host, post) tuple.
cheers


@gosom Please read my comment again. I said the version of the code in the requests toolbelt works correctly. That code is available [here](https://github.com/sigmavirus24/requests-toolbelt/blob/master/requests_toolbelt/adapters/source.py).


@Lukasa 
yes you are right. I was not even aware of the request-toolbelt :+1:  

Thanks for the reply


it seems this method does not work with current version of requests, it reports "socket.gaierror: [Errno -9] Address family for hostname not supported" during binding. 


There is no reason to assume that this has become broken. Can you show an example of your code please?

```
class SourceAddressAdapter(HTTPAdapter):
    def __init__(self, source_address, **kwargs):
        self.source_address = source_address
        super(SourceAddressAdapter, self).__init__(**kwargs)

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       source_address=self.source_address)


def prepare_sessions(ip):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s = requests.session()
    s.mount("http://", SourceAddressAdapter((str(ip), 0)))
    s.mount("https://", SourceAddressAdapter((str(ip), 0)))
    return s 

def test(s): 
    print("{}".format(s.get("http://bot.whatismyipaddress.com").text))



if __name__ == "__main__": 
    s = prepare_sessions(sys.argv[1]) 
    test(s) 
```

Can you also show me the full traceback and the value of `sys.argv[1]` in this case?

anything wrong with the code? I have a server with 5 ips, and when I use primary ip, it works fine, but when I tried with other ips, it reports error as below 
```
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/connection.py", line 137, in _new_conn
    (self.host, self.port), self.timeout, **extra_kw)
  File "/usr/lib/python3/dist-packages/urllib3/util/connection.py", line 91, in create_connection
    raise err
  File "/usr/lib/python3/dist-packages/urllib3/util/connection.py", line 80, in create_connection
    sock.bind(source_address)
socket.gaierror: [Errno -9] Address family for hostname not supported

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 560, in urlopen
    body=body, headers=headers)
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 354, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "/usr/lib/python3.5/http/client.py", line 1106, in request
    self._send_request(method, url, body, headers)
  File "/usr/lib/python3.5/http/client.py", line 1151, in _send_request
    self.endheaders(body)
  File "/usr/lib/python3.5/http/client.py", line 1102, in endheaders
    self._send_output(message_body)
  File "/usr/lib/python3.5/http/client.py", line 934, in _send_output
    self.send(msg)
  File "/usr/lib/python3.5/http/client.py", line 877, in send
    self.connect()
  File "/usr/lib/python3/dist-packages/urllib3/connection.py", line 162, in connect
    conn = self._new_conn()
  File "/usr/lib/python3/dist-packages/urllib3/connection.py", line 146, in _new_conn
    self, "Failed to establish a new connection: %s" % e)
requests.packages.urllib3.exceptions.NewConnectionError: <requests.packages.urllib3.connection.HTTPConnection object at 0x7fba0cccb668>: Failed to establish a new connection: [Errno -9] Address family for hostname not supported

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/requests/adapters.py", line 376, in send
    timeout=timeout
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 610, in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/usr/lib/python3/dist-packages/urllib3/util/retry.py", line 273, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
requests.packages.urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='bot.whatismyipaddress.com', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x7fba0cccb668>: Failed to establish a new connection: [Errno -9] Address family for hostname not supported',))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 58, in <module>
    test(s) 
  File "test.py", line 52, in test
    print("{}".format(s.get("http://bot.whatismyipaddress.com").text))
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 480, in get
    return self.request('GET', url, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 468, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/lib/python3/dist-packages/requests/sessions.py", line 576, in send
    r = adapter.send(request, **kwargs)
  File "/usr/lib/python3/dist-packages/requests/adapters.py", line 437, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='bot.whatismyipaddress.com', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x7fba0cccb668>: Failed to establish a new connection: [Errno -9] Address family for hostname not supported',))
``` 

the value passed is an ip string such as 172.82.134.69 

the same piece of code worked one and half years ago, I just copied over, but it seems not working, maybe I missed something? 

So my best guess is that this is going to be related to IP address families: specifically, IPv4 and IPv6. `bot.whatismyipaddress.com` has an IPv6 address, which is probably why you're seeing this error.

However, this error should only appear if all connection attempts failed, and presumably the IPv4 ones would not. So it'd be interesting to know why the other connection attempts fail. Mind using `strace` to trace the various socket syscalls to see what's happening?

the related error is 
```
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("172.82.134.67")}, 16) = -1 EADDRNOTAVAIL (Cannot assign requested address)
``` 
does that mean the server cannot use the ip? 

Certainly suggests so. Are you sure that IP is present on your machine?

I rented the dedicated server with 5 ips, (4 usable), the ips are shown in control panel, how can I check in the system? Really appreciate your help! 

`ifconfig` should show you.

it looks like this 
```
eno1      Link encap:Ethernet  HWaddr 00:8c:fa:05:21:c4  
          inet addr:172.82.134.66  Bcast:172.82.134.71  Mask:255.255.255.248
          inet6 addr: fe80::28c:faff:fe05:21c4/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6636840 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5852373 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:4217736834 (4.2 GB)  TX bytes:850693240 (850.6 MB)
          Memory:c0000000-c001ffff 
``` 
the mask seems correct, how should I interpret this? 

No, your address is .66, and you put .67 above.

The mask just indicates what your machine believes its subnet is, not what addresses it believes is assigned to it.

but I have /29 subnet and it has additional ips range from 172.82.134.67 to 172.82.134.69, how can I use the ips? 

You need to tell your machine you own them. `ip addr add 172.82.134.67` should work.

Great! It works now! Thank you for helping!