diff --git a/httpx/auth.py b/httpx/auth.py index e0ef50c3a0..e412c5707f 100644 --- a/httpx/auth.py +++ b/httpx/auth.py @@ -6,7 +6,7 @@ from base64 import b64encode from urllib.request import parse_http_list -from .exceptions import ProtocolError +from .exceptions import ProtocolError, RequestBodyUnavailable from .models import Request, Response from .utils import to_bytes, to_str, unquote @@ -104,6 +104,8 @@ def __init__( self.password = to_bytes(password) def __call__(self, request: Request) -> AuthFlow: + if not request.stream.can_replay(): + raise RequestBodyUnavailable("Request body is no longer available.") response = yield request if response.status_code != 401 or "www-authenticate" not in response.headers: diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index ea6ff8acac..34ec77eb4b 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -5,7 +5,15 @@ import pytest -from httpx import URL, AsyncClient, DigestAuth, ProtocolError, Request, Response +from httpx import ( + URL, + AsyncClient, + DigestAuth, + ProtocolError, + Request, + RequestBodyUnavailable, + Response, +) from httpx.auth import Auth, AuthFlow from httpx.config import CertTypes, TimeoutTypes, VerifyTypes from httpx.dispatch.base import Dispatcher @@ -442,3 +450,16 @@ def __call__(self, request: Request) -> AuthFlow: assert resp2.history == [resp1] assert len(resp1.history) == 0 + + +@pytest.mark.asyncio +async def test_digest_auth_unavailable_streaming_body(): + url = "https://example.org/" + auth = DigestAuth(username="tomchristie", password="password123") + client = AsyncClient(dispatch=MockDispatch()) + + async def streaming_body(): + yield b"Example request body" + + with pytest.raises(RequestBodyUnavailable): + await client.post(url, data=streaming_body(), auth=auth)