PoolTimeout,
ProtocolError,
ReadTimeout,
+ RedirectBodyUnavailable,
RedirectLoop,
ResponseClosed,
StreamConsumed,
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
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:
"""
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
"""
+class RedirectBodyUnavailable(RedirectError):
+ """
+ Got a redirect response, but the request body was streaming, and is
+ no longer available.
+ """
+
+
class RedirectLoop(RedirectError):
"""
Infinite redirect loop.
[
("continue", 100),
("switching_protocols", 101),
+ ("processing", 102),
("ok", 200),
("created", 201),
("accepted", 202),
("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),
URL,
Adapter,
RedirectAdapter,
+ RedirectBodyUnavailable,
RedirectLoop,
Request,
Response,
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)
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())