tomchristie: Fantastic stuff!

I guess this PR should also update the [`test_start_tls_on_socket_stream`](https://github.com/encode/httpx/blob/1a32cf036a825f6eb35395af5388a3b23180a82e/tests/test_concurrency.py#L35) test case, right?
tomchristie: At a later date I may look at moving this out to a coverage-excluded `compat.py` module, so that we can tweak our tests to expect 100% coverage on any single run, without requiring collection across multiple Python versions. (Buit I'm very happy to treat that seperately)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1cea654..34d47ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## 0.7.7 (November 15, 2019)
+
+### Fixed
+
+- Resolve an issue with cookies behavior on redirect requests. (Pull #529)
+
+### Added
+
+- Add request/response DEBUG logs. (Pull #502)
+- Use TRACE log level for low level info. (Pull #500)
+
 ## 0.7.6 (November 2, 2019)
 
 ### Removed
diff --git a/httpx/__version__.py b/httpx/__version__.py
index 106c345..5783d84 100644
--- a/httpx/__version__.py
+++ b/httpx/__version__.py
@@ -1,3 +1,3 @@
 __title__ = "httpx"
 __description__ = "A next generation HTTP client, for Python 3."
-__version__ = "0.7.6"
+__version__ = "0.7.7"
diff --git a/httpx/concurrency/asyncio.py b/httpx/concurrency/asyncio.py
index 010d821..019876e 100644
--- a/httpx/concurrency/asyncio.py
+++ b/httpx/concurrency/asyncio.py
@@ -58,16 +58,49 @@ class SocketStream(BaseSocketStream):
         self, hostname: str, ssl_context: ssl.SSLContext, timeout: TimeoutConfig
     ) -> "SocketStream":
         loop = asyncio.get_event_loop()
-        if not hasattr(loop, "start_tls"):  # pragma: no cover
-            raise NotImplementedError(
-                "asyncio.AbstractEventLoop.start_tls() is only available in Python 3.7+"
-            )
 
         stream_reader = asyncio.StreamReader()
         protocol = asyncio.StreamReaderProtocol(stream_reader)
         transport = self.stream_writer.transport
 
-        loop_start_tls = loop.start_tls  # type: ignore
+        if hasattr(loop, "start_tls"):
+            loop_start_tls = loop.start_tls  # type: ignore
+        else:
+
+            async def loop_start_tls(
+                transport: asyncio.BaseTransport,
+                protocol: asyncio.BaseProtocol,
+                sslcontext: ssl.SSLContext = None,
+                *,
+                server_side: bool = False,
+                server_hostname: str = None,
+                ssl_handshake_timeout: float = None,
+            ) -> asyncio.Transport:
+                """Python 3.6 asyncio doesn't have a start_tls() method on the loop
+                so we use this function in place of the loop's start_tls() method.
+                Adapted from this comment:
+                https://github.com/urllib3/urllib3/issues/1323#issuecomment-362494839
+                """
+                import asyncio.sslproto
+
+                waiter = loop.create_future()
+                ssl_protocol = asyncio.sslproto.SSLProtocol(
+                    loop,
+                    protocol,
+                    sslcontext,
+                    waiter,
+                    server_side=False,
+                    server_hostname=server_hostname,
+                    call_connection_made=False,
+                )
+
+                transport.set_protocol(ssl_protocol)
+                loop.call_soon(ssl_protocol.connection_made, transport)
+                loop.call_soon(transport.resume_reading)  # type: ignore
+
+                await waiter
+                return ssl_protocol._app_transport
+
         transport = await asyncio.wait_for(
             loop_start_tls(
                 transport=transport,
diff --git a/httpx/middleware/redirect.py b/httpx/middleware/redirect.py
index 6f5c02e..e093bf4 100644
--- a/httpx/middleware/redirect.py
+++ b/httpx/middleware/redirect.py
@@ -51,7 +51,6 @@ class RedirectMiddleware(BaseMiddleware):
         headers = self.redirect_headers(request, url, method)  # TODO: merge headers?
         content = self.redirect_content(request, method)
         cookies = Cookies(self.cookies)
-        cookies.update(request.cookies)
         return AsyncRequest(
             method=method, url=url, headers=headers, data=content, cookies=cookies
         )
@@ -115,6 +114,11 @@ class RedirectMiddleware(BaseMiddleware):
             # are only relevant to the request body.
             headers.pop("Content-Length", None)
             headers.pop("Transfer-Encoding", None)
+
+        # We should use the client cookie store to determine any cookie header,
+        # rather than whatever was on the original outgoing request.
+        headers.pop("Cookie", None)
+
         return headers
 
     def redirect_content(self, request: AsyncRequest, method: str) -> bytes:
diff --git a/noxfile.py b/noxfile.py
index ef524eb..43494b5 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -24,14 +24,7 @@ def lint(session):
 @nox.session
 def check(session):
     session.install(
-        "--upgrade",
-        "black",
-        "flake8",
-        "flake8-bugbear",
-        "flake8-comprehensions",
-        "flake8-pie",
-        "isort",
-        "mypy",
+        "--upgrade", "black", "flake8", "flake8-bugbear", "flake8-pie", "isort", "mypy"
     )
 
     session.run("black", "--check", "--diff", "--target-version=py36", *source_files)

HTTPX claims Python 3.6+ support when asyncio backend TCPStream.start_tls explicitly does not support Python 3.6
As stated, HTTPX explicitly does not support Python 3.6 [here](https://github.com/encode/httpx/blob/master/httpx/concurrency/asyncio.py#L61) but declares Python 3.6 support [here](https://github.com/encode/httpx/blob/master/setup.py#L40). This has caused problems with users of a package of mine that is supposed to support Python 3.6.
diff --git a/test-requirements.txt b/test-requirements.txt
index 7aa572c..96437e0 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -6,7 +6,6 @@ brotlipy==0.7.*
 cryptography
 flake8
 flake8-bugbear
-flake8-comprehensions
 flake8-pie
 isort
 mypy
diff --git a/tests/client/test_redirects.py b/tests/client/test_redirects.py
index d732ce4..b014b0f 100644
--- a/tests/client/test_redirects.py
+++ b/tests/client/test_redirects.py
@@ -290,3 +290,71 @@ async def test_cross_subdomain_redirect(backend):
     url = "https://example.com/cross_subdomain"
     response = await client.get(url)
     assert response.url == URL("https://www.example.org/cross_subdomain")
+
+
+class MockCookieDispatch(AsyncDispatcher):
+    async def send(
+        self,
+        request: AsyncRequest,
+        verify: VerifyTypes = None,
+        cert: CertTypes = None,
+        timeout: TimeoutTypes = None,
+    ) -> AsyncResponse:
+        if request.url.path == "/":
+            if "cookie" in request.headers:
+                content = b"Logged in"
+            else:
+                content = b"Not logged in"
+            return AsyncResponse(codes.OK, content=content, request=request)
+
+        elif request.url.path == "/login":
+            status_code = codes.SEE_OTHER
+            headers = {
+                "location": "/",
+                "set-cookie": (
+                    "session=eyJ1c2VybmFtZSI6ICJ0b21; path=/; Max-Age=1209600; "
+                    "httponly; samesite=lax"
+                ),
+            }
+
+            return AsyncResponse(status_code, headers=headers, request=request)
+
+        elif request.url.path == "/logout":
+            status_code = codes.SEE_OTHER
+            headers = {
+                "location": "/",
+                "set-cookie": (
+                    "session=null; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; "
+                    "httponly; samesite=lax"
+                ),
+            }
+            return AsyncResponse(status_code, headers=headers, request=request)
+
+
+async def test_redirect_cookie_behavior(backend):
+    client = AsyncClient(dispatch=MockCookieDispatch(), backend=backend)
+
+    # The client is not logged in.
+    response = await client.get("https://example.com/")
+    assert response.url == "https://example.com/"
+    assert response.text == "Not logged in"
+
+    # Login redirects to the homepage, setting a session cookie.
+    response = await client.post("https://example.com/login")
+    assert response.url == "https://example.com/"
+    assert response.text == "Logged in"
+
+    # The client is logged in.
+    response = await client.get("https://example.com/")
+    assert response.url == "https://example.com/"
+    assert response.text == "Logged in"
+
+    # Logout redirects to the homepage, expiring the session cookie.
+    response = await client.post("https://example.com/logout")
+    assert response.url == "https://example.com/"
+    assert response.text == "Not logged in"
+
+    # The client is not logged in.
+    response = await client.get("https://example.com/")
+    assert response.url == "https://example.com/"
+    assert response.text == "Not logged in"
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index 8bb933b..7477ea3 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -1,5 +1,3 @@
-import sys
-
 import pytest
 import trio
 
@@ -31,9 +29,6 @@ async def test_start_tls_on_socket_stream(https_server, backend, get_cipher):
     See that the concurrency backend can make a connection without TLS then
     start TLS on an existing connection.
     """
-    if isinstance(backend, AsyncioBackend) and sys.version_info < (3, 7):
-        pytest.xfail(reason="Requires Python 3.7+ for AbstractEventLoop.start_tls()")
-
     ctx = SSLConfig().load_ssl_context_no_verify(HTTPVersionConfig())
     timeout = TimeoutConfig(5)
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-asyncio pytest-trio pytest-cov trio trustme uvicorn
3.7
test-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==24.2.0
brotlipy==0.7.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
chardet==3.0.4
click==8.1.8
coverage==7.2.7
cryptography==44.0.2
exceptiongroup==1.2.2
flake8==5.0.4
flake8-bugbear==23.3.12
flake8-comprehensions==3.13.0
flake8-pie==0.16.0
h11==0.8.1
h2==3.2.0
hpack==3.0.0
hstspreload==2025.1.1
-e git+https://github.com/encode/httpx.git@1a32cf036a825f6eb35395af5388a3b23180a82e#egg=httpx
hyperframe==5.2.0
idna==2.10
importlib-metadata==4.2.0
iniconfig==2.0.0
isort==5.11.5
mccabe==0.7.0
mypy==1.4.1
mypy-extensions==1.0.0
outcome==1.3.0.post0
packaging==24.0
pluggy==1.2.0
pycodestyle==2.9.1
pycparser==2.21
pyflakes==2.5.0
pytest==7.4.4
pytest-asyncio==0.21.2
pytest-cov==4.1.0
pytest-trio==0.8.0
rfc3986==1.5.0
sniffio==1.3.1
sortedcontainers==2.4.0
tomli==2.0.1
trio==0.22.2
trustme==1.0.0
typed-ast==1.5.5
typing_extensions==4.7.1
uvicorn==0.22.0
zipp==3.15.0

name: httpx
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==24.2.0
      - brotlipy==0.7.0
      - cffi==1.15.1
      - chardet==3.0.4
      - click==8.1.8
      - coverage==7.2.7
      - cryptography==44.0.2
      - exceptiongroup==1.2.2
      - flake8==5.0.4
      - flake8-bugbear==23.3.12
      - flake8-comprehensions==3.13.0
      - flake8-pie==0.16.0
      - h11==0.8.1
      - h2==3.2.0
      - hpack==3.0.0
      - hstspreload==2025.1.1
      - hyperframe==5.2.0
      - idna==2.10
      - importlib-metadata==4.2.0
      - iniconfig==2.0.0
      - isort==5.11.5
      - mccabe==0.7.0
      - mypy==1.4.1
      - mypy-extensions==1.0.0
      - outcome==1.3.0.post0
      - packaging==24.0
      - pluggy==1.2.0
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pyflakes==2.5.0
      - pytest==7.4.4
      - pytest-asyncio==0.21.2
      - pytest-cov==4.1.0
      - pytest-trio==0.8.0
      - rfc3986==1.5.0
      - sniffio==1.3.1
      - sortedcontainers==2.4.0
      - tomli==2.0.1
      - trio==0.22.2
      - trustme==1.0.0
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
      - uvicorn==0.22.0
      - zipp==3.15.0
prefix: /opt/conda/envs/httpx

tests/client/test_redirects.py::test_redirect_cookie_behavior[AsyncioBackend] tests/client/test_redirects.py::test_redirect_cookie_behavior[TrioBackend]
tests/test_concurrency.py::test_start_tls_on_socket_stream[backend0-<lambda>] tests/test_concurrency.py::test_start_tls_on_socket_stream[backend1-<lambda>]
tests/client/test_redirects.py::test_no_redirect[AsyncioBackend] tests/client/test_redirects.py::test_no_redirect[TrioBackend] tests/client/test_redirects.py::test_redirect_301[AsyncioBackend] tests/client/test_redirects.py::test_redirect_301[TrioBackend] tests/client/test_redirects.py::test_redirect_302[AsyncioBackend] tests/client/test_redirects.py::test_redirect_302[TrioBackend] tests/client/test_redirects.py::test_redirect_303[AsyncioBackend] tests/client/test_redirects.py::test_redirect_303[TrioBackend] tests/client/test_redirects.py::test_disallow_redirects[AsyncioBackend] tests/client/test_redirects.py::test_disallow_redirects[TrioBackend] tests/client/test_redirects.py::test_relative_redirect[AsyncioBackend] tests/client/test_redirects.py::test_relative_redirect[TrioBackend] tests/client/test_redirects.py::test_no_scheme_redirect[AsyncioBackend] tests/client/test_redirects.py::test_no_scheme_redirect[TrioBackend] tests/client/test_redirects.py::test_fragment_redirect[AsyncioBackend] tests/client/test_redirects.py::test_fragment_redirect[TrioBackend] tests/client/test_redirects.py::test_multiple_redirects[AsyncioBackend] tests/client/test_redirects.py::test_multiple_redirects[TrioBackend] tests/client/test_redirects.py::test_too_many_redirects[AsyncioBackend] tests/client/test_redirects.py::test_too_many_redirects[TrioBackend] tests/client/test_redirects.py::test_too_many_redirects_calling_next[AsyncioBackend] tests/client/test_redirects.py::test_too_many_redirects_calling_next[TrioBackend] tests/client/test_redirects.py::test_redirect_loop[AsyncioBackend] tests/client/test_redirects.py::test_redirect_loop[TrioBackend] tests/client/test_redirects.py::test_redirect_loop_calling_next[AsyncioBackend] tests/client/test_redirects.py::test_redirect_loop_calling_next[TrioBackend] tests/client/test_redirects.py::test_cross_domain_redirect[AsyncioBackend] tests/client/test_redirects.py::test_cross_domain_redirect[TrioBackend] tests/client/test_redirects.py::test_same_domain_redirect[AsyncioBackend] tests/client/test_redirects.py::test_same_domain_redirect[TrioBackend] tests/client/test_redirects.py::test_body_redirect[AsyncioBackend] tests/client/test_redirects.py::test_body_redirect[TrioBackend] tests/client/test_redirects.py::test_no_body_redirect[AsyncioBackend] tests/client/test_redirects.py::test_no_body_redirect[TrioBackend] tests/client/test_redirects.py::test_cannot_redirect_streaming_body[AsyncioBackend] tests/client/test_redirects.py::test_cannot_redirect_streaming_body[TrioBackend] tests/client/test_redirects.py::test_cross_subdomain_redirect[AsyncioBackend] tests/client/test_redirects.py::test_cross_subdomain_redirect[TrioBackend]
httpx/middleware/redirect.py httpx/__version__.py CHANGELOG.md httpx/concurrency/asyncio.py noxfile.py
httpx/middleware/redirect.py httpx/__version__.py CHANGELOG.md httpx/concurrency/asyncio.py noxfile.py