Sessions apparently corrupting header values on subsequent requests

After reading http://docs.python-requests.org/en/master/user/advanced/#session-objects I tried setting request headers on a session to ensure that all requests made using that session object would include those headers with their values unchanged. However, I am running into a strange corruption issue that I would like to share with you.

## Expected Result

That the `X-Api-Key` header I set on the session would be correctly sent to the server, unchanged, on all requests made using the session object.

## Actual Result

For some reason, the value of the `X-Api-Key` header is being changed and causing a 401 error when authenticating to the API endpoint I am trying to contact.

## Reproduction Steps

```python
import requests

print requests.__version__

s = requests.Session()
s.headers.update({
    "X-Api-Key": '<redacted>TU=',
    "Accept-Encoding": "gzip, deflate",
    "User-Agent": "magnet-sdk-python",
    "Accept": "application/json"
})

response = s.request('GET', 'https://api.niddel.com/v2/me')
print response.status_code

response = s.request('GET', 'https://api.niddel.com/v2/me')
print response.status_code
```

The expected output for this is:
```
2.18.4
200
200
```

What I consistently get instead is:
```
2.18.4
200
401
```

More importantly, on the server-side logs I enabled logging of the first and last few characters of the API key for troubleshooting purposes. On the first case, they match the (valid) API key I wrote on the test code. In the failed request, the last few characters inexplicably changed from `xTU=` to `+w==`.

So for some reason the `X-Api-Key` header contents are being corrupted on the 2nd and later requests of a session. Curiously enough, the same thing does not happen if I perform completely separate requests:
```
import requests

headers = {"X-Api-Key": '<redacted>TU=',
           "Accept-Encoding": "gzip, deflate",
           "User-Agent": "magnet-sdk-python",
           "Accept": "application/json"}

response = requests.request('GET', 'https://api.niddel.com/v2/me', headers=headers)
print response.status_code

response = requests.request('GET', 'https://api.niddel.com/v2/me', headers=headers)
print response.status_code
```

Which gives me two 200 status codes, as expected.

I thought long and hard about whether this could be a server-side error related to multiple requests on persistent connections. But I have plenty of code that uses libcurl and also does requests like these using persistent connections at a large scale, and this error never manifested itself. So I am thinking this might be an issue on requests (or perhaps urllib3).

## System Information

    $ python -m requests.help

```
{
  "chardet": {
    "version": "3.0.4"
  },
  "cryptography": {
    "version": ""
  },
  "idna": {
    "version": ""
  },
  "implementation": {
    "name": "CPython",
    "version": "2.7.10"
  },
  "platform": {
    "release": "16.7.0",
    "system": "Darwin"
  },
  "pyOpenSSL": {
    "openssl_version": "",
    "version": null
  },
  "requests": {
    "version": "2.18.4"
  },
  "system_ssl": {
    "version": "9081df"
  },
  "urllib3": {
    "version": "1.22"
  },
  "using_pyopenssl": false
}
```

This is an example using the [R binding for libcurl](https://cran.r-project.org/web/packages/curl/index.html) that consistently works as intended:
```
> library(curl)
> h <- new_handle()
> handle_setheaders(h, "X-Api-Key" = "<redacted>tTU=", "Accept-Encoding" = "gzip, deflate", "User-Agent" = "magnet-sdk-R", "Accept" = "application/json")
> curl_fetch_memory("https://api.niddel.com/v2/me", h)$status
[1] 200
> curl_fetch_memory("https://api.niddel.com/v2/me", h)$status
[1] 200
> curl_fetch_memory("https://api.niddel.com/v2/me", h)$status
[1] 200
> curl_fetch_memory("https://api.niddel.com/v2/me", h)$status
[1] 200
```

Does this reproduce if you change the host to `httpbin.org`?

Argh, heisenbug. Now I can't reproduce it even with my own API.

The weird thing, though, is that accessing https://httpbin.org/headers with a session, it tells me the request was sent with `Connection: close` which is unexpected if HTTP persistent connections are meant to be used.

```
import requests
import json

print requests.__version__

s = requests.Session()
s.headers.update({
    "X-Api-Key": '1234567890123456789012345678901234567890123=',
    "Accept-Encoding": "gzip, deflate",
    "User-Agent": "magnet-sdk-python",
    "Accept": "application/json"
})

response = s.request('GET', 'https://httpbin.org/headers')
print response.status_code
print json.dumps(response.json(), indent=4)
print response.request.headers
```

Which generates the following output:
```
2.18.4
200
{
    "headers": {
        "Accept-Encoding": "gzip, deflate", 
        "Host": "httpbin.org", 
        "Accept": "application/json", 
        "User-Agent": "magnet-sdk-python", 
        "Connection": "close", 
        "X-Api-Key": "1234567890123456789012345678901234567890123="
    }
}
{'Connection': 'keep-alive', 'X-Api-Key': '1234567890123456789012345678901234567890123=', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'User-Agent': 'magnet-sdk-python'}
```

I found it particularly interesting that response.request.headers had `Connection: keep-alive` but that was not what httpbin.org received. 🤔

Now about the original problem, a few additional pieces of information:
* I've seen this not only running directly from OS X, but also on a Docker container and a customer Linux machine. And the problem stopped after I stopped using sessions and hasn't recurred ever since (https://github.com/Niddel/magnet-api2-sdk-python/commit/47e2795b8bf88f1990106ed000b8fa8113a332ed);
* Looking at the logs, the last digits of the actually submitted API key vary on each request, indicating this might be some sort of memory overrun or corruption issue somewhere along the line.

> The weird thing, though, is that accessing https://httpbin.org/headers with a session, it tells me the request was sent with `Connection: close` which is unexpected if HTTP persistent connections are meant to be used.

`httpbin.org` will lie to you. :wink: It sits behind a reverse proxy or two, and they're the ones setting `Connection: close`. Wireshark will show you that we don't.

If there's a memory overrun issue it won't be in our library, but it could be in a dependency. It would be *very* interesting to see if you can pin down where it's coming from.

Ah, reverse proxies make sense indeed for the `Connection` header. Thank you for clarifying that.

BTW I ran my test again and got the corruption again. Heisenbug FTW. :)

I have a workaround that is working for me, will circle back to this issue when I can. But I wanted to document this here in case this pops up again from someone else, in which case it might help.

Also, keep in mind "memory overrun" was just a wildly uninformed speculation on my part. I'll leave you to determine if there's any chance that the requests code could be doing weird things with header contents or not.

Finally, thanks one more time for the wonderful library, requests is just plain awesome. ❤️ 

I may be hitting this problem as well. Basically I do (pseudocode):

```python
url = 'https://remote.com/v0/api'
headers = {'Authorization': 'Bearer <access_token>'}

while True:

    client = requests.Session()
    client.headers.update(headers)
    client.stream = True

    response = client.get(url, params=params, timeout=timeout)

    if response.status_code != 200:
        time.delay(10)
        continue
    
    for line in response.iter_lines():
        if not line:
            continue
        process_line(line)
```

This works all week just fine, but once in a week (on Monday morning), I start receiving this 401 response status codes non-stop ("Insufficient authorization to perform request."). Now, if I kill the process and restart it it will work just fine (i.e.: no code change, same headers, same API URL, same params...).

Note that I am actually creating a new `Session` after each failure, not reusing the old one.

Could it be related to this issue? Do not know if it could be related to #2949 instead (or as well).

@asieira Which workaround did you use?

@Peque my workaround was to not use sessions anymore, basically, as per https://github.com/Niddel/magnet-api2-sdk-python/commit/47e2795b8bf88f1990106ed000b8fa8113a332ed. Can you try doing the same and see if this solves your problem as well?

@Peque it looks like you shouldn't be actually running into this. You always create a new session and reconfigure the headers (based on that snippet).

There's not enough here to get to the bottom of the problem and I have code that works pretty much the same way as @asieira's without issue, so I'm going to close this. New information would be helpful but should be put into a new issue and reference this one.