We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
h11._util.LocalProtocolError: Too little data for declared Content-Length
client POC:

```python
import asyncio
import logging
import httpx

logging.basicConfig(level=logging.DEBUG)


async def test():
    client = httpx.AsyncClient()
    url = 'http://127.0.0.1:8000/debug'
    resp = await client.post(url, data='debug', allow_redirects=True)
    print(resp.content)


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(test())
```

app POC:

```python
from starlette.responses import RedirectResponse
from starlette.applications import Starlette
import uvicorn

app = Starlette(debug=True)

@app.route('/debug', methods=['POST', 'GET'])
async def debug(request):
    return RedirectResponse(url='https://httpbin.org/headers', status_code=302)

if __name__ == '__main__':
    uvicorn.run(app)
```

log:
```
DEBUG:asyncio:Using selector: KqueueSelector
DEBUG:httpx.dispatch.connection_pool:new_connection connection=HTTPConnection(origin=Origin(scheme='http' host='127.0.0.1' port=8000))
DEBUG:httpx.dispatch.connection:start_connect host='127.0.0.1' port=8000 timeout=TimeoutConfig(timeout=5.0)
DEBUG:httpx.dispatch.connection:connected http_version='HTTP/1.1'
DEBUG:httpx.dispatch.http11:send_headers method='POST' target='/debug' headers=Headers({'host': '127.0.0.1:8000', 'user-agent': 'python-httpx/0.7.2', 'accept': '*/*', 'content-length': '5', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
DEBUG:httpx.dispatch.http11:receive_event event=NEED_DATA
DEBUG:httpx.dispatch.http11:send_data data=Data(<5 bytes>)
DEBUG:httpx.dispatch.http11:receive_event event=Response(status_code=302, headers=[(b'date', b'Tue, 03 Sep 2019 03:50:53 GMT'), (b'server', b'uvicorn'), (b'location', b'https://httpbin.org/headers'), (b'transfer-encoding', b'chunked')], http_version=b'1.1', reason=b'Found')
DEBUG:httpx.dispatch.http11:receive_event event=EndOfMessage(headers=[])
DEBUG:httpx.dispatch.http11:response_closed our_state=DONE their_state=DONE
DEBUG:httpx.dispatch.connection_pool:release_connection connection=HTTPConnection(origin=Origin(scheme='http' host='127.0.0.1' port=8000))
DEBUG:httpx.dispatch.connection_pool:new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='httpbin.org' port=443))
DEBUG:httpx.dispatch.connection:start_connect host='httpbin.org' port=443 timeout=TimeoutConfig(timeout=5.0)
DEBUG:httpx.dispatch.connection:connected http_version='HTTP/1.1'
DEBUG:httpx.dispatch.http11:send_headers method='GET' target='/headers' headers=Headers({'host': 'httpbin.org', 'user-agent': 'python-httpx/0.7.2', 'accept': '*/*', 'content-length': '5', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
DEBUG:httpx.dispatch.http11:receive_event event=NEED_DATA


Traceback (most recent call last):
  File "http3/httpx/dispatch/http11.py", line 53, in send
    http_version, status_code, headers = await self._receive_response(timeout)
  File "http3/httpx/dispatch/http11.py", line 133, in _receive_response
    event = await self._receive_event(timeout)
  File "http3/httpx/dispatch/http11.py", line 174, in _receive_event
    self.READ_NUM_BYTES, timeout, flag=self.timeout_flag
  File "http3/httpx/concurrency/asyncio.py", line 92, in read
    raise ReadTimeout() from None
http3.httpx.exceptions.ReadTimeout

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test_proto.py", line 16, in <module>
    asyncio.get_event_loop().run_until_complete(test())
  File "/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "test_proto.py", line 11, in test
    resp = await client.post(url, data='debug', allow_redirects=True)
  File "http3/httpx/client.py", line 415, in post
    trust_env=trust_env,
  File "http3/httpx/client.py", line 566, in request
    trust_env=trust_env,
  File "http3/httpx/client.py", line 237, in send
    return await get_response(request)
  File "http3/httpx/middleware.py", line 72, in __call__
    return await self(next_request, get_response)
  File "http3/httpx/middleware.py", line 62, in __call__
    response = await get_response(request)
  File "http3/httpx/client.py", line 202, in get_response
    request, verify=verify, cert=cert, timeout=timeout
  File "http3/httpx/dispatch/connection_pool.py", line 126, in send
    raise exc
  File "http3/httpx/dispatch/connection_pool.py", line 121, in send
    request, verify=verify, cert=cert, timeout=timeout
  File "http3/httpx/dispatch/connection.py", line 65, in send
    response = await self.h11_connection.send(request, timeout=timeout)
  File "http3/httpx/dispatch/http11.py", line 53, in send
    http_version, status_code, headers = await self._receive_response(timeout)
  File "http3/httpx/concurrency/asyncio.py", line 285, in __aexit__
    await self.task
  File "http3/httpx/dispatch/http11.py", line 108, in _send_request_data
    await self._send_event(event, timeout)
  File "http3/httpx/dispatch/http11.py", line 123, in _send_event
    bytes_to_send = self.h11_state.send(event)
  File "site-packages/h11/_connection.py", line 469, in send
    data_list = self.send_with_data_passthrough(event)
  File "site-packages/h11/_connection.py", line 502, in send_with_data_passthrough
    writer(event, data_list.append)
  File "site-packages/h11/_writers.py", line 79, in __call__
    self.send_eom(event.headers, write)
  File "site-packages/h11/_writers.py", line 102, in send_eom
    raise LocalProtocolError("Too little data for declared Content-Length")
h11._util.LocalProtocolError: Too little data for declared Content-Length
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `httpx/middleware.py`
Content:
```
1 import functools
2 import typing
3 from base64 import b64encode
4 
5 from .config import DEFAULT_MAX_REDIRECTS
6 from .exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects
7 from .models import URL, AsyncRequest, AsyncResponse, Cookies, Headers
8 from .status_codes import codes
9 
10 
11 class BaseMiddleware:
12     async def __call__(
13         self, request: AsyncRequest, get_response: typing.Callable
14     ) -> AsyncResponse:
15         raise NotImplementedError  # pragma: no cover
16 
17 
18 class BasicAuthMiddleware(BaseMiddleware):
19     def __init__(
20         self, username: typing.Union[str, bytes], password: typing.Union[str, bytes]
21     ):
22         if isinstance(username, str):
23             username = username.encode("latin1")
24 
25         if isinstance(password, str):
26             password = password.encode("latin1")
27 
28         userpass = b":".join((username, password))
29         token = b64encode(userpass).decode().strip()
30 
31         self.authorization_header = f"Basic {token}"
32 
33     async def __call__(
34         self, request: AsyncRequest, get_response: typing.Callable
35     ) -> AsyncResponse:
36         request.headers["Authorization"] = self.authorization_header
37         return await get_response(request)
38 
39 
40 class CustomAuthMiddleware(BaseMiddleware):
41     def __init__(self, auth: typing.Callable[[AsyncRequest], AsyncRequest]):
42         self.auth = auth
43 
44     async def __call__(
45         self, request: AsyncRequest, get_response: typing.Callable
46     ) -> AsyncResponse:
47         request = self.auth(request)
48         return await get_response(request)
49 
50 
51 class RedirectMiddleware(BaseMiddleware):
52     def __init__(
53         self,
54         allow_redirects: bool = True,
55         max_redirects: int = DEFAULT_MAX_REDIRECTS,
56         cookies: typing.Optional[Cookies] = None,
57     ):
58         self.allow_redirects = allow_redirects
59         self.max_redirects = max_redirects
60         self.cookies = cookies
61         self.history: typing.List[AsyncResponse] = []
62 
63     async def __call__(
64         self, request: AsyncRequest, get_response: typing.Callable
65     ) -> AsyncResponse:
66         if len(self.history) > self.max_redirects:
67             raise TooManyRedirects()
68         if request.url in (response.url for response in self.history):
69             raise RedirectLoop()
70 
71         response = await get_response(request)
72         response.history = list(self.history)
73 
74         if not response.is_redirect:
75             return response
76 
77         self.history.append(response)
78         next_request = self.build_redirect_request(request, response)
79 
80         if self.allow_redirects:
81             return await self(next_request, get_response)
82 
83         response.call_next = functools.partial(self, next_request, get_response)
84         return response
85 
86     def build_redirect_request(
87         self, request: AsyncRequest, response: AsyncResponse
88     ) -> AsyncRequest:
89         method = self.redirect_method(request, response)
90         url = self.redirect_url(request, response)
91         headers = self.redirect_headers(request, url)  # TODO: merge headers?
92         content = self.redirect_content(request, method)
93         cookies = Cookies(self.cookies)
94         cookies.update(request.cookies)
95         return AsyncRequest(
96             method=method, url=url, headers=headers, data=content, cookies=cookies
97         )
98 
99     def redirect_method(self, request: AsyncRequest, response: AsyncResponse) -> str:
100         """
101         When being redirected we may want to change the method of the request
102         based on certain specs or browser behavior.
103         """
104         method = request.method
105 
106         # https://tools.ietf.org/html/rfc7231#section-6.4.4
107         if response.status_code == codes.SEE_OTHER and method != "HEAD":
108             method = "GET"
109 
110         # Do what the browsers do, despite standards...
111         # Turn 302s into GETs.
112         if response.status_code == codes.FOUND and method != "HEAD":
113             method = "GET"
114 
115         # If a POST is responded to with a 301, turn it into a GET.
116         # This bizarre behaviour is explained in 'requests' issue 1704.
117         if response.status_code == codes.MOVED_PERMANENTLY and method == "POST":
118             method = "GET"
119 
120         return method
121 
122     def redirect_url(self, request: AsyncRequest, response: AsyncResponse) -> URL:
123         """
124         Return the URL for the redirect to follow.
125         """
126         location = response.headers["Location"]
127 
128         url = URL(location, allow_relative=True)
129 
130         # Facilitate relative 'Location' headers, as allowed by RFC 7231.
131         # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
132         if url.is_relative_url:
133             url = request.url.join(url)
134 
135         # Attach previous fragment if needed (RFC 7231 7.1.2)
136         if request.url.fragment and not url.fragment:
137             url = url.copy_with(fragment=request.url.fragment)
138 
139         return url
140 
141     def redirect_headers(self, request: AsyncRequest, url: URL) -> Headers:
142         """
143         Strip Authorization headers when responses are redirected away from
144         the origin.
145         """
146         headers = Headers(request.headers)
147         if url.origin != request.url.origin:
148             del headers["Authorization"]
149             del headers["host"]
150         return headers
151 
152     def redirect_content(self, request: AsyncRequest, method: str) -> bytes:
153         """
154         Return the body that should be used for the redirect request.
155         """
156         if method != request.method and method == "GET":
157             return b""
158         if request.is_streaming:
159             raise RedirectBodyUnavailable()
160         return request.content
161
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/httpx/middleware.py b/httpx/middleware.py
--- a/httpx/middleware.py
+++ b/httpx/middleware.py
@@ -88,7 +88,7 @@
     ) -> AsyncRequest:
         method = self.redirect_method(request, response)
         url = self.redirect_url(request, response)
-        headers = self.redirect_headers(request, url)  # TODO: merge headers?
+        headers = self.redirect_headers(request, url, method)  # TODO: merge headers?
         content = self.redirect_content(request, method)
         cookies = Cookies(self.cookies)
         cookies.update(request.cookies)
@@ -138,15 +138,24 @@
 
         return url
 
-    def redirect_headers(self, request: AsyncRequest, url: URL) -> Headers:
+    def redirect_headers(self, request: AsyncRequest, url: URL, method: str) -> Headers:
         """
-        Strip Authorization headers when responses are redirected away from
-        the origin.
+        Return the headers that should be used for the redirect request.
         """
         headers = Headers(request.headers)
+
         if url.origin != request.url.origin:
+            # Strip Authorization headers when responses are redirected away from
+            # the origin.
             del headers["Authorization"]
-            del headers["host"]
+            del headers["Host"]
+
+        if method != request.method and method == "GET":
+            # If we've switch to a 'GET' request, then strip any headers which
+            # are only relevant to the request body.
+            del headers["Content-Length"]
+            del headers["Transfer-Encoding"]
+
         return headers
 
     def redirect_content(self, request: AsyncRequest, method: str) -> bytes:

{"golden_diff": "diff --git a/httpx/middleware.py b/httpx/middleware.py\n--- a/httpx/middleware.py\n+++ b/httpx/middleware.py\n@@ -88,7 +88,7 @@\n     ) -> AsyncRequest:\n         method = self.redirect_method(request, response)\n         url = self.redirect_url(request, response)\n-        headers = self.redirect_headers(request, url)  # TODO: merge headers?\n+        headers = self.redirect_headers(request, url, method)  # TODO: merge headers?\n         content = self.redirect_content(request, method)\n         cookies = Cookies(self.cookies)\n         cookies.update(request.cookies)\n@@ -138,15 +138,24 @@\n \n         return url\n \n-    def redirect_headers(self, request: AsyncRequest, url: URL) -> Headers:\n+    def redirect_headers(self, request: AsyncRequest, url: URL, method: str) -> Headers:\n         \"\"\"\n-        Strip Authorization headers when responses are redirected away from\n-        the origin.\n+        Return the headers that should be used for the redirect request.\n         \"\"\"\n         headers = Headers(request.headers)\n+\n         if url.origin != request.url.origin:\n+            # Strip Authorization headers when responses are redirected away from\n+            # the origin.\n             del headers[\"Authorization\"]\n-            del headers[\"host\"]\n+            del headers[\"Host\"]\n+\n+        if method != request.method and method == \"GET\":\n+            # If we've switch to a 'GET' request, then strip any headers which\n+            # are only relevant to the request body.\n+            del headers[\"Content-Length\"]\n+            del headers[\"Transfer-Encoding\"]\n+\n         return headers\n \n     def redirect_content(self, request: AsyncRequest, method: str) -> bytes:\n", "issue": "h11._util.LocalProtocolError: Too little data for declared Content-Length\nclient POC:\r\n\r\n```python\r\nimport asyncio\r\nimport logging\r\nimport httpx\r\n\r\nlogging.basicConfig(level=logging.DEBUG)\r\n\r\n\r\nasync def test():\r\n    client = httpx.AsyncClient()\r\n    url = 'http://127.0.0.1:8000/debug'\r\n    resp = await client.post(url, data='debug', allow_redirects=True)\r\n    print(resp.content)\r\n\r\n\r\nif __name__ == '__main__':\r\n    asyncio.get_event_loop().run_until_complete(test())\r\n```\r\n\r\napp POC:\r\n\r\n```python\r\nfrom starlette.responses import RedirectResponse\r\nfrom starlette.applications import Starlette\r\nimport uvicorn\r\n\r\napp = Starlette(debug=True)\r\n\r\n@app.route('/debug', methods=['POST', 'GET'])\r\nasync def debug(request):\r\n    return RedirectResponse(url='https://httpbin.org/headers', status_code=302)\r\n\r\nif __name__ == '__main__':\r\n    uvicorn.run(app)\r\n```\r\n\r\nlog:\r\n```\r\nDEBUG:asyncio:Using selector: KqueueSelector\r\nDEBUG:httpx.dispatch.connection_pool:new_connection connection=HTTPConnection(origin=Origin(scheme='http' host='127.0.0.1' port=8000))\r\nDEBUG:httpx.dispatch.connection:start_connect host='127.0.0.1' port=8000 timeout=TimeoutConfig(timeout=5.0)\r\nDEBUG:httpx.dispatch.connection:connected http_version='HTTP/1.1'\r\nDEBUG:httpx.dispatch.http11:send_headers method='POST' target='/debug' headers=Headers({'host': '127.0.0.1:8000', 'user-agent': 'python-httpx/0.7.2', 'accept': '*/*', 'content-length': '5', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})\r\nDEBUG:httpx.dispatch.http11:receive_event event=NEED_DATA\r\nDEBUG:httpx.dispatch.http11:send_data data=Data(<5 bytes>)\r\nDEBUG:httpx.dispatch.http11:receive_event event=Response(status_code=302, headers=[(b'date', b'Tue, 03 Sep 2019 03:50:53 GMT'), (b'server', b'uvicorn'), (b'location', b'https://httpbin.org/headers'), (b'transfer-encoding', b'chunked')], http_version=b'1.1', reason=b'Found')\r\nDEBUG:httpx.dispatch.http11:receive_event event=EndOfMessage(headers=[])\r\nDEBUG:httpx.dispatch.http11:response_closed our_state=DONE their_state=DONE\r\nDEBUG:httpx.dispatch.connection_pool:release_connection connection=HTTPConnection(origin=Origin(scheme='http' host='127.0.0.1' port=8000))\r\nDEBUG:httpx.dispatch.connection_pool:new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='httpbin.org' port=443))\r\nDEBUG:httpx.dispatch.connection:start_connect host='httpbin.org' port=443 timeout=TimeoutConfig(timeout=5.0)\r\nDEBUG:httpx.dispatch.connection:connected http_version='HTTP/1.1'\r\nDEBUG:httpx.dispatch.http11:send_headers method='GET' target='/headers' headers=Headers({'host': 'httpbin.org', 'user-agent': 'python-httpx/0.7.2', 'accept': '*/*', 'content-length': '5', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})\r\nDEBUG:httpx.dispatch.http11:receive_event event=NEED_DATA\r\n\r\n\r\nTraceback (most recent call last):\r\n  File \"http3/httpx/dispatch/http11.py\", line 53, in send\r\n    http_version, status_code, headers = await self._receive_response(timeout)\r\n  File \"http3/httpx/dispatch/http11.py\", line 133, in _receive_response\r\n    event = await self._receive_event(timeout)\r\n  File \"http3/httpx/dispatch/http11.py\", line 174, in _receive_event\r\n    self.READ_NUM_BYTES, timeout, flag=self.timeout_flag\r\n  File \"http3/httpx/concurrency/asyncio.py\", line 92, in read\r\n    raise ReadTimeout() from None\r\nhttp3.httpx.exceptions.ReadTimeout\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTraceback (most recent call last):\r\n  File \"test_proto.py\", line 16, in <module>\r\n    asyncio.get_event_loop().run_until_complete(test())\r\n  File \"/lib/python3.7/asyncio/base_events.py\", line 584, in run_until_complete\r\n    return future.result()\r\n  File \"test_proto.py\", line 11, in test\r\n    resp = await client.post(url, data='debug', allow_redirects=True)\r\n  File \"http3/httpx/client.py\", line 415, in post\r\n    trust_env=trust_env,\r\n  File \"http3/httpx/client.py\", line 566, in request\r\n    trust_env=trust_env,\r\n  File \"http3/httpx/client.py\", line 237, in send\r\n    return await get_response(request)\r\n  File \"http3/httpx/middleware.py\", line 72, in __call__\r\n    return await self(next_request, get_response)\r\n  File \"http3/httpx/middleware.py\", line 62, in __call__\r\n    response = await get_response(request)\r\n  File \"http3/httpx/client.py\", line 202, in get_response\r\n    request, verify=verify, cert=cert, timeout=timeout\r\n  File \"http3/httpx/dispatch/connection_pool.py\", line 126, in send\r\n    raise exc\r\n  File \"http3/httpx/dispatch/connection_pool.py\", line 121, in send\r\n    request, verify=verify, cert=cert, timeout=timeout\r\n  File \"http3/httpx/dispatch/connection.py\", line 65, in send\r\n    response = await self.h11_connection.send(request, timeout=timeout)\r\n  File \"http3/httpx/dispatch/http11.py\", line 53, in send\r\n    http_version, status_code, headers = await self._receive_response(timeout)\r\n  File \"http3/httpx/concurrency/asyncio.py\", line 285, in __aexit__\r\n    await self.task\r\n  File \"http3/httpx/dispatch/http11.py\", line 108, in _send_request_data\r\n    await self._send_event(event, timeout)\r\n  File \"http3/httpx/dispatch/http11.py\", line 123, in _send_event\r\n    bytes_to_send = self.h11_state.send(event)\r\n  File \"site-packages/h11/_connection.py\", line 469, in send\r\n    data_list = self.send_with_data_passthrough(event)\r\n  File \"site-packages/h11/_connection.py\", line 502, in send_with_data_passthrough\r\n    writer(event, data_list.append)\r\n  File \"site-packages/h11/_writers.py\", line 79, in __call__\r\n    self.send_eom(event.headers, write)\r\n  File \"site-packages/h11/_writers.py\", line 102, in send_eom\r\n    raise LocalProtocolError(\"Too little data for declared Content-Length\")\r\nh11._util.LocalProtocolError: Too little data for declared Content-Length\r\n```\r\n\n", "before_files": [{"content": "import functools\nimport typing\nfrom base64 import b64encode\n\nfrom .config import DEFAULT_MAX_REDIRECTS\nfrom .exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects\nfrom .models import URL, AsyncRequest, AsyncResponse, Cookies, Headers\nfrom .status_codes import codes\n\n\nclass BaseMiddleware:\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        raise NotImplementedError  # pragma: no cover\n\n\nclass BasicAuthMiddleware(BaseMiddleware):\n    def __init__(\n        self, username: typing.Union[str, bytes], password: typing.Union[str, bytes]\n    ):\n        if isinstance(username, str):\n            username = username.encode(\"latin1\")\n\n        if isinstance(password, str):\n            password = password.encode(\"latin1\")\n\n        userpass = b\":\".join((username, password))\n        token = b64encode(userpass).decode().strip()\n\n        self.authorization_header = f\"Basic {token}\"\n\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        request.headers[\"Authorization\"] = self.authorization_header\n        return await get_response(request)\n\n\nclass CustomAuthMiddleware(BaseMiddleware):\n    def __init__(self, auth: typing.Callable[[AsyncRequest], AsyncRequest]):\n        self.auth = auth\n\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        request = self.auth(request)\n        return await get_response(request)\n\n\nclass RedirectMiddleware(BaseMiddleware):\n    def __init__(\n        self,\n        allow_redirects: bool = True,\n        max_redirects: int = DEFAULT_MAX_REDIRECTS,\n        cookies: typing.Optional[Cookies] = None,\n    ):\n        self.allow_redirects = allow_redirects\n        self.max_redirects = max_redirects\n        self.cookies = cookies\n        self.history: typing.List[AsyncResponse] = []\n\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        if len(self.history) > self.max_redirects:\n            raise TooManyRedirects()\n        if request.url in (response.url for response in self.history):\n            raise RedirectLoop()\n\n        response = await get_response(request)\n        response.history = list(self.history)\n\n        if not response.is_redirect:\n            return response\n\n        self.history.append(response)\n        next_request = self.build_redirect_request(request, response)\n\n        if self.allow_redirects:\n            return await self(next_request, get_response)\n\n        response.call_next = functools.partial(self, next_request, get_response)\n        return response\n\n    def build_redirect_request(\n        self, request: AsyncRequest, response: AsyncResponse\n    ) -> AsyncRequest:\n        method = self.redirect_method(request, response)\n        url = self.redirect_url(request, response)\n        headers = self.redirect_headers(request, url)  # TODO: merge headers?\n        content = self.redirect_content(request, method)\n        cookies = Cookies(self.cookies)\n        cookies.update(request.cookies)\n        return AsyncRequest(\n            method=method, url=url, headers=headers, data=content, cookies=cookies\n        )\n\n    def redirect_method(self, request: AsyncRequest, response: AsyncResponse) -> str:\n        \"\"\"\n        When being redirected we may want to change the method of the request\n        based on certain specs or browser behavior.\n        \"\"\"\n        method = request.method\n\n        # https://tools.ietf.org/html/rfc7231#section-6.4.4\n        if response.status_code == codes.SEE_OTHER and method != \"HEAD\":\n            method = \"GET\"\n\n        # Do what the browsers do, despite standards...\n        # Turn 302s into GETs.\n        if response.status_code == codes.FOUND and method != \"HEAD\":\n            method = \"GET\"\n\n        # If a POST is responded to with a 301, turn it into a GET.\n        # This bizarre behaviour is explained in 'requests' issue 1704.\n        if response.status_code == codes.MOVED_PERMANENTLY and method == \"POST\":\n            method = \"GET\"\n\n        return method\n\n    def redirect_url(self, request: AsyncRequest, response: AsyncResponse) -> URL:\n        \"\"\"\n        Return the URL for the redirect to follow.\n        \"\"\"\n        location = response.headers[\"Location\"]\n\n        url = URL(location, allow_relative=True)\n\n        # Facilitate relative 'Location' headers, as allowed by RFC 7231.\n        # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')\n        if url.is_relative_url:\n            url = request.url.join(url)\n\n        # Attach previous fragment if needed (RFC 7231 7.1.2)\n        if request.url.fragment and not url.fragment:\n            url = url.copy_with(fragment=request.url.fragment)\n\n        return url\n\n    def redirect_headers(self, request: AsyncRequest, url: URL) -> Headers:\n        \"\"\"\n        Strip Authorization headers when responses are redirected away from\n        the origin.\n        \"\"\"\n        headers = Headers(request.headers)\n        if url.origin != request.url.origin:\n            del headers[\"Authorization\"]\n            del headers[\"host\"]\n        return headers\n\n    def redirect_content(self, request: AsyncRequest, method: str) -> bytes:\n        \"\"\"\n        Return the body that should be used for the redirect request.\n        \"\"\"\n        if method != request.method and method == \"GET\":\n            return b\"\"\n        if request.is_streaming:\n            raise RedirectBodyUnavailable()\n        return request.content\n", "path": "httpx/middleware.py"}], "after_files": [{"content": "import functools\nimport typing\nfrom base64 import b64encode\n\nfrom .config import DEFAULT_MAX_REDIRECTS\nfrom .exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects\nfrom .models import URL, AsyncRequest, AsyncResponse, Cookies, Headers\nfrom .status_codes import codes\n\n\nclass BaseMiddleware:\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        raise NotImplementedError  # pragma: no cover\n\n\nclass BasicAuthMiddleware(BaseMiddleware):\n    def __init__(\n        self, username: typing.Union[str, bytes], password: typing.Union[str, bytes]\n    ):\n        if isinstance(username, str):\n            username = username.encode(\"latin1\")\n\n        if isinstance(password, str):\n            password = password.encode(\"latin1\")\n\n        userpass = b\":\".join((username, password))\n        token = b64encode(userpass).decode().strip()\n\n        self.authorization_header = f\"Basic {token}\"\n\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        request.headers[\"Authorization\"] = self.authorization_header\n        return await get_response(request)\n\n\nclass CustomAuthMiddleware(BaseMiddleware):\n    def __init__(self, auth: typing.Callable[[AsyncRequest], AsyncRequest]):\n        self.auth = auth\n\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        request = self.auth(request)\n        return await get_response(request)\n\n\nclass RedirectMiddleware(BaseMiddleware):\n    def __init__(\n        self,\n        allow_redirects: bool = True,\n        max_redirects: int = DEFAULT_MAX_REDIRECTS,\n        cookies: typing.Optional[Cookies] = None,\n    ):\n        self.allow_redirects = allow_redirects\n        self.max_redirects = max_redirects\n        self.cookies = cookies\n        self.history: typing.List[AsyncResponse] = []\n\n    async def __call__(\n        self, request: AsyncRequest, get_response: typing.Callable\n    ) -> AsyncResponse:\n        if len(self.history) > self.max_redirects:\n            raise TooManyRedirects()\n        if request.url in (response.url for response in self.history):\n            raise RedirectLoop()\n\n        response = await get_response(request)\n        response.history = list(self.history)\n\n        if not response.is_redirect:\n            return response\n\n        self.history.append(response)\n        next_request = self.build_redirect_request(request, response)\n\n        if self.allow_redirects:\n            return await self(next_request, get_response)\n\n        response.call_next = functools.partial(self, next_request, get_response)\n        return response\n\n    def build_redirect_request(\n        self, request: AsyncRequest, response: AsyncResponse\n    ) -> AsyncRequest:\n        method = self.redirect_method(request, response)\n        url = self.redirect_url(request, response)\n        headers = self.redirect_headers(request, url, method)  # TODO: merge headers?\n        content = self.redirect_content(request, method)\n        cookies = Cookies(self.cookies)\n        cookies.update(request.cookies)\n        return AsyncRequest(\n            method=method, url=url, headers=headers, data=content, cookies=cookies\n        )\n\n    def redirect_method(self, request: AsyncRequest, response: AsyncResponse) -> str:\n        \"\"\"\n        When being redirected we may want to change the method of the request\n        based on certain specs or browser behavior.\n        \"\"\"\n        method = request.method\n\n        # https://tools.ietf.org/html/rfc7231#section-6.4.4\n        if response.status_code == codes.SEE_OTHER and method != \"HEAD\":\n            method = \"GET\"\n\n        # Do what the browsers do, despite standards...\n        # Turn 302s into GETs.\n        if response.status_code == codes.FOUND and method != \"HEAD\":\n            method = \"GET\"\n\n        # If a POST is responded to with a 301, turn it into a GET.\n        # This bizarre behaviour is explained in 'requests' issue 1704.\n        if response.status_code == codes.MOVED_PERMANENTLY and method == \"POST\":\n            method = \"GET\"\n\n        return method\n\n    def redirect_url(self, request: AsyncRequest, response: AsyncResponse) -> URL:\n        \"\"\"\n        Return the URL for the redirect to follow.\n        \"\"\"\n        location = response.headers[\"Location\"]\n\n        url = URL(location, allow_relative=True)\n\n        # Facilitate relative 'Location' headers, as allowed by RFC 7231.\n        # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')\n        if url.is_relative_url:\n            url = request.url.join(url)\n\n        # Attach previous fragment if needed (RFC 7231 7.1.2)\n        if request.url.fragment and not url.fragment:\n            url = url.copy_with(fragment=request.url.fragment)\n\n        return url\n\n    def redirect_headers(self, request: AsyncRequest, url: URL, method: str) -> Headers:\n        \"\"\"\n        Return the headers that should be used for the redirect request.\n        \"\"\"\n        headers = Headers(request.headers)\n\n        if url.origin != request.url.origin:\n            # Strip Authorization headers when responses are redirected away from\n            # the origin.\n            del headers[\"Authorization\"]\n            del headers[\"Host\"]\n\n        if method != request.method and method == \"GET\":\n            # If we've switch to a 'GET' request, then strip any headers which\n            # are only relevant to the request body.\n            del headers[\"Content-Length\"]\n            del headers[\"Transfer-Encoding\"]\n\n        return headers\n\n    def redirect_content(self, request: AsyncRequest, method: str) -> bytes:\n        \"\"\"\n        Return the body that should be used for the redirect request.\n        \"\"\"\n        if method != request.method and method == \"GET\":\n            return b\"\"\n        if request.is_streaming:\n            raise RedirectBodyUnavailable()\n        return request.content\n", "path": "httpx/middleware.py"}]}