Update host header on redirect.

If you manually specify HTTP Host header, any redirect to host different from specified will result in TooManyRedirects exception. 

Example:

```
>>> import requests
>>> requests.get('http://reddit.com', headers={"Host": "reddit.com"})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "requests/api.py", line 55, in get
    return request('get', url, **kwargs)
  File "requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "requests/sessions.py", line 357, in request
    resp = self.send(prep, **send_kwargs)
  File "requests/sessions.py", line 480, in send
    history = [resp for resp in gen] if allow_redirects else []
  File "requests/sessions.py", line 82, in resolve_redirects
    raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects)
requests.exceptions.TooManyRedirects: Exceeded 30 redirects.
```

After receiving first 301 response, requests library makes a request for url specified in it's "Location" field (in the example above it was http://www.reddit.com/), but it also preserves all headers set by user, so you end up having header "Host: reddit.com" while host is now **www**.reddit.com. Server checks "host" header and responds with 301 again. And now we're in a loop.

I'd be glad to add tests for this fix, but httpbin does not support subdomains.


Thanks for this! The Pull Request itself looks good.

Before we merge this, I want to nail down a few things because it's not clear to me this this fix won't cause people problems. In particular, if you've set `Host` explicitly you clearly feel that Requests is going to choose an incorrect value for that header. If you're then 302'ed to the _same_ domain, or with a relative URI, this will overwrite your previous header with the new one which is presumably not acceptable. Is that a real problem, or have I made that up?


>  if you've set Host explicitly you clearly feel that Requests is going to choose an incorrect value for that header

It can also be used to work around missing DNS for that host, e.g.:

``` python

requests.get('http://127.0.0.1/', headers={'Host': 'my-hostname.com'})

```


@jkbr Agreed, but that falls into the problem I suggested: if that host does a relative redirect this change will wipe your previous Host header.


@Lukasa Right, It's quite tricky. 

I guess a solution could be to remember that a custom `Host` has been supplied and use it only as an alias of the one included in the request URL for any subsequent requests to the same host within a session. Let me try to demonstrate:

``` python

# Request a page that redirects and specify custom Host:
>>> response = requests.get('http://127.0.0.1/', headers={'Host': 'my-hostname.com'})
>>> response.headers['Location']
'http://my-hostname.com/foo'


# And this time follow:
>>> response2 = requests.get('http://127.0.0.1/',  headers={'Host': 'my-hostname.com'}, follow=True)
>>> response2.url
'http://my-hostname.com/foo' 
# We know that the Location hostname is an alias because it was 
# included in the custom Host header of the first request.
# Therefore, we used the original URL host for the redirect location instead:
>>> response2.request.url  
'http://127.0.0.1/foo'                


# Redirect handling (pseudocode)
if 'Location' in response.headers:
    location = get_absolute_url(response.headers['Location'])
    if 'Host' in prepared_request.headers:
        # Custom Host provided
        if urlparse(location).hostname == prepared_request.headers['Host']:
            location = location.replace(
                prepared_request.headers['Host'], 
                urlparse(prepared_request.url).hostname,
                1
           )

    print('Redirect to', location)

```


See, that feels like massive overkill to me. For stuff like this it's generally been expected that the user will just take over redirection themselves (e.g. set `allow_redirects=False`).


I agree. It's an edge case and handling it properly would add a lot of complexity. Throwing an exception with an explanatory message might be a good idea, though.


Seems like healthy choice here is to update "Host" only when hostname in original url and "Location" url is different.

```
# this code is now present in requesets/sessions.py
if not urlparse(url).netloc:                                                                                                                                                                 
    url = urljoin(resp.url, requote_uri(url))  # url gets "reconstructed" if we encountered relative Location                                                                                                                                            
else:                                                                                                                                                                                      
    url = requote_uri(url)

# this is new code
is_same_hostname = urlparse(resp.request.url).hostname == urlparse(url).hostname
if 'Host' in prepared_request.headers and not is_same_hostname:
    prepared_request.headers['Host'] = urlparse(url).hostname
```

The only case when user can encounter redirection Loop in this case is when request is constructed like this:

```
requests.get('http://www.reddit.com', headers={"Host": "reddit.com"})
```

and it's obvious programming error on the side of the user.

Edit: How you can test this:

Following requests return 200:

```
# 54.225.138.124 is current ip of httpbin.
requests.get('http://54.225.138.124/redirect-to?url=/', headers={"Host": "httpbin.org"})  # Host is not rewritten, since redirect is relative (hence the same)
requests.get('http://54.225.138.124/redirect-to?url=http://54.225.138.124/', headers={"Host": "httpbin.org"})  # Host is not rewritten since redirect points to the same hostname
requests.get('http://reddit.com', headers={"Host": "reddit.com"})  # host is rewritten to www.reddit.com since redirect points to another hostname
```


@Scorpil That's not a bad idea, but I disagree about your final statement. When written like that it's an obvious programming error, but when hidden in variables and loops it's absolutely not clear. _Especially_ as it was the example you gave in your original post. =)

We have two options: we write this so that you cannot hit this redirection loop, or we say "Hey, if you're playing with the `Host` header you want to worry about redirect loops. Maybe turn `allow_redirects` off?".


Good point. Fortunately, it's not hard to detect situation when "Host" does not match the url before raising TooManyRedirects exception and correct error message accordingly. Kind of like this but should be nicer in final version:

```
if i >= self.max_redirects:                                                                                                                                                                     
    message = 'Exceeded %s redirects.' % self.max_redirects                                                                                                                                     
    if 'Host' in prepared_request.headers and prepared_request.headers['Host'] != urlparse(resp.url).hostname:                                                                                  
        message += ' Warning: HTTP Host header does not match URL domain'                                                                                                                      
    raise TooManyRedirects(message)
```


I think that's probably the route we want to take with this.


@Scorpil _Ping_.

Want to update this PR?


Whoops... Sorry. Update on it's way.


Any feedback on update, @Lukasa?


Looks good to me. One comment in the diff, otherwise I'm happy. =)


It's a little verbose, but i hope also helpful.


Cool, I'm happy. =) Add yourself to the AUTHORS file and your work here will be done. Thanks so much! ;cake:


Done. I'm glad I could help.


@Scorpil can you rebase your pull request? Something like this should work:

```
git remote add upstream git://github.com/kennethreitz/master
git fetch upstream
git rebase upstream/master
git push -f
```

Note that there will be conflicts while rebasing so you should be very careful in resolving them and us in reviewing the resulting diff.


Done. The only conflict was in AUTHORS file. And i've checked manually requests/sessions.py: changes there are compatible with mine, fast-forward did the job.


Thanks @Scorpil !


My gut tells me to just recommend users sending their own host headers to just handle their own redirects.


@kennethreitz I have avoided being too negative about this, but I agree with you. I don't see anything wrong with the current behaviour. We're inconveniencing users who are doing something we strongly recommend against, that doesn't seem like a bad thing frankly.


@Scorpil keep the pull requests coming! This was a really great change! Sorry we couldn't accept it right now :)

Everything about your contribution was perfect, though. Requests just is overly opinionated :)


Thanks @kennethreitz 
No problem, if you think it's not appropriate change - then it's not. No hard feelings :)


@Scorpil Here's some :cake:. You deserve it. :)


Yeah, this was a really well-handled pull request, thanks. =) Exemplary work, you're a model contributor.