]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Handle body in redirects
authorTom Christie <tom@tomchristie.com>
Mon, 29 Apr 2019 14:25:44 +0000 (15:25 +0100)
committerTom Christie <tom@tomchristie.com>
Mon, 29 Apr 2019 14:25:44 +0000 (15:25 +0100)
httpcore/__init__.py
httpcore/adapters/redirects.py
httpcore/exceptions.py
httpcore/status_codes.py
tests/adapters/test_redirects.py

index fdd992534fd338df34f0e7fab4290ee4daa898c5..813b2b56c0332149dee28a7382d9865ddcc53f3d 100644 (file)
@@ -10,6 +10,7 @@ from .exceptions import (
     PoolTimeout,
     ProtocolError,
     ReadTimeout,
+    RedirectBodyUnavailable,
     RedirectLoop,
     ResponseClosed,
     StreamConsumed,
index aa6c557e5743f0d36309b44ddebcdbd6db5f8159..584efec26e6d9cbc992eee092a51dd1ba2a8e1b7 100644 (file)
@@ -2,7 +2,7 @@ import typing
 from urllib.parse import urljoin, urlparse
 
 from ..config import DEFAULT_MAX_REDIRECTS
-from ..exceptions import RedirectLoop, TooManyRedirects
+from ..exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects
 from ..interfaces import Adapter
 from ..models import URL, Headers, Request, Response
 from ..status_codes import codes
@@ -44,7 +44,8 @@ class RedirectAdapter(Adapter):
         method = self.redirect_method(request, response)
         url = self.redirect_url(request, response)
         headers = self.redirect_headers(request, url)
-        return Request(method=method, url=url, headers=headers)
+        body = self.redirect_body(request, method)
+        return Request(method=method, url=url, headers=headers, body=body)
 
     def redirect_method(self, request: Request, response: Response) -> str:
         """
@@ -97,3 +98,10 @@ class RedirectAdapter(Adapter):
         if url.origin != request.url.origin:
             del headers["Authorization"]
         return headers
+
+    def redirect_body(self, request: Request, method: str) -> bytes:
+        if method != request.method and method == "GET":
+            return b""
+        if request.is_streaming:
+            raise RedirectBodyUnavailable()
+        return request.body
index 12aac53b852560931766eacc32e0dabc6ed2c16f..cf8d3f18ecff2175d4ec6dda508fb90da008779e 100644 (file)
@@ -40,6 +40,13 @@ class TooManyRedirects(RedirectError):
     """
 
 
+class RedirectBodyUnavailable(RedirectError):
+    """
+    Got a redirect response, but the request body was streaming, and is
+    no longer available.
+    """
+
+
 class RedirectLoop(RedirectError):
     """
     Infinite redirect loop.
index 19b849f045119a18505344389afd65b2bdc02c94..6a224d04349334aec6018b3f5b9d296053e8ca8d 100644 (file)
@@ -5,6 +5,7 @@ codes = enum.IntEnum(
     [
         ("continue", 100),
         ("switching_protocols", 101),
+        ("processing", 102),
         ("ok", 200),
         ("created", 201),
         ("accepted", 202),
@@ -13,14 +14,16 @@ codes = enum.IntEnum(
         ("reset_content", 205),
         ("partial_content", 206),
         ("multi_status", 207),
+        ("already_reported", 208),
+        ("im_used", 226),
         ("multiple_choices", 300),
         ("moved_permanently", 301),
         ("found", 302),
         ("see_other", 303),
         ("not_modified", 304),
         ("use_proxy", 305),
-        ("reserved", 306),
         ("temporary_redirect", 307),
+        ("permanent_redirect", 308),
         ("bad_request", 400),
         ("unauthorized", 401),
         ("payment_required", 402),
index ab371a047d481ec3479c966ce535743f7f94c842..6499c762f2e77ee5c18b0505b54e6bd26f4005a6 100644 (file)
@@ -7,6 +7,7 @@ from httpcore import (
     URL,
     Adapter,
     RedirectAdapter,
+    RedirectBodyUnavailable,
     RedirectLoop,
     Request,
     Response,
@@ -74,6 +75,14 @@ class MockDispatch(Adapter):
             body = json.dumps({"headers": headers}).encode()
             return Response(codes.ok, body=body, request=request)
 
+        elif request.url.path == "/redirect_body":
+            headers = [(b"location", b"/redirect_body_target")]
+            return Response(codes.permanent_redirect, headers=headers, request=request)
+
+        elif request.url.path == "/redirect_body_target":
+            body = json.dumps({"body": request.body.decode()}).encode()
+            return Response(codes.ok, body=body, request=request)
+
         return Response(codes.ok, body=b"Hello, world!", request=request)
 
 
@@ -178,3 +187,26 @@ async def test_same_domain_redirect():
     data = json.loads(response.body.decode())
     assert response.url == URL("https://example.org/cross_domain_target")
     assert data == {"headers": {"authorization": "abc"}}
+
+
+@pytest.mark.asyncio
+async def test_body_redirect():
+    client = RedirectAdapter(MockDispatch())
+    body = b"Example request body"
+    url = "https://example.org/redirect_body"
+    response = await client.request("POST", url, body=body)
+    data = json.loads(response.body.decode())
+    assert response.url == URL("https://example.org/redirect_body_target")
+    assert data == {"body": "Example request body"}
+
+
+@pytest.mark.asyncio
+async def test_cannot_redirect_streaming_body():
+    client = RedirectAdapter(MockDispatch())
+    url = "https://example.org/redirect_body"
+
+    async def body():
+        yield b"Example request body"
+
+    with pytest.raises(RedirectBodyUnavailable):
+        await client.request("POST", url, body=body())