async def test_aiter_raw(): stream = AsyncIteratorStream(aiterator=async_streaming_body()) response = httpx.Response(200, stream=stream, request=REQUEST) raw = b"" async for part in response.aiter_raw(): raw += part assert raw == b"Hello, world!"
async def test_text_decoder(data, encoding): async def iterator(): nonlocal data for chunk in data: yield chunk # Accessing `.text` on a read response. stream = AsyncIteratorStream(aiterator=iterator()) response = httpx.Response(200, stream=stream, request=REQUEST) await response.aread() assert response.text == (b"".join(data)).decode(encoding) # Streaming `.aiter_text` iteratively. stream = AsyncIteratorStream(aiterator=iterator()) response = httpx.Response(200, stream=stream, request=REQUEST) text = "".join([part async for part in response.aiter_text()]) assert text == (b"".join(data)).decode(encoding)
async def test_elapsed_not_available_until_closed(): stream = AsyncIteratorStream(aiterator=async_streaming_body()) response = httpx.Response( 200, stream=stream, ) with pytest.raises(RuntimeError): response.elapsed
async def test_cannot_aread_after_stream_consumed(): stream = AsyncIteratorStream(aiterator=async_streaming_body()) response = httpx.Response(200, stream=stream, request=REQUEST) content = b"" async for part in response.aiter_bytes(): content += part with pytest.raises(httpx.StreamConsumed): await response.aread()
async def test_async_streaming_response(): stream = AsyncIteratorStream(aiterator=async_streaming_body()) response = httpx.Response(200, stream=stream, request=REQUEST) assert response.status_code == 200 assert not response.is_closed content = await response.aread() assert content == b"Hello, world!" assert response.content == b"Hello, world!" assert response.is_closed
async def request( self, method: bytes, url: typing.Tuple[bytes, bytes, int, bytes], headers: typing.List[typing.Tuple[bytes, bytes]] = None, stream: httpcore.AsyncByteStream = None, timeout: typing.Dict[str, typing.Optional[float]] = None, ) -> typing.Tuple[bytes, int, bytes, typing.List[typing.Tuple[ bytes, bytes]], httpcore.AsyncByteStream, ]: content = AsyncIteratorStream(aiterator=(part async for part in stream)) return b"HTTP/1.1", 200, b"OK", [], content
async def test_aiter_raw_increments_updates_counter(): stream = AsyncIteratorStream(aiterator=async_streaming_body()) response = httpx.Response( 200, stream=stream, ) num_downloaded = response.num_bytes_downloaded async for part in response.aiter_raw(): assert len(part) == (response.num_bytes_downloaded - num_downloaded) num_downloaded = response.num_bytes_downloaded
async def test_text_decoder_known_encoding(): async def iterator(): yield b"\x83g" yield b"\x83" yield b"\x89\x83x\x83\x8b" stream = AsyncIteratorStream(aiterator=iterator()) response = httpx.Response( 200, headers=[(b"Content-Type", b"text/html; charset=shift-jis")], stream=stream, ) await response.aread() assert "".join(response.text) == "トラベル"
async def test_streaming(): body = b"test 123" compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) async def compress(body): yield compressor.compress(body) yield compressor.flush() headers = [(b"Content-Encoding", b"gzip")] stream = AsyncIteratorStream(aiterator=compress(body)) response = httpx.Response(200, headers=headers, stream=stream, request=REQUEST) assert not hasattr(response, "body") assert await response.aread() == body
async def test_cannot_aread_after_response_closed(): is_closed = False async def close_func(): nonlocal is_closed is_closed = True stream = AsyncIteratorStream(aiterator=async_streaming_body(), close_func=close_func) response = httpx.Response(200, stream=stream, request=REQUEST) await response.aclose() assert is_closed with pytest.raises(httpx.ResponseClosed): await response.aread()
def _request( self, method: bytes, url: typing.Tuple[bytes, bytes, int, bytes], headers: typing.List[typing.Tuple[bytes, bytes]], stream: ContentStream, timeout: typing.Dict[str, typing.Optional[float]] = None, ) -> typing.Tuple[bytes, int, bytes, typing.List[typing.Tuple[ bytes, bytes]], ContentStream]: scheme, host, port, path = url path, _, query = path.partition(b"?") if path == b"/no_redirect": return b"HTTP/1.1", codes.OK, b"OK", [], ByteStream(b"") elif path == b"/redirect_301": async def body(): yield b"<a href='https://example.org/'>here</a>" status_code = codes.MOVED_PERMANENTLY headers = [(b"location", b"https://example.org/")] stream = AsyncIteratorStream(aiterator=body()) return b"HTTP/1.1", status_code, b"Moved Permanently", headers, stream elif path == b"/redirect_302": status_code = codes.FOUND headers = [(b"location", b"https://example.org/")] return b"HTTP/1.1", status_code, b"Found", headers, ByteStream(b"") elif path == b"/redirect_303": status_code = codes.SEE_OTHER headers = [(b"location", b"https://example.org/")] return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream( b"") elif path == b"/relative_redirect": status_code = codes.SEE_OTHER headers = [(b"location", b"/")] return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream( b"") elif path == b"/malformed_redirect": status_code = codes.SEE_OTHER headers = [(b"location", b"https://:443/")] return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream( b"") elif path == b"/no_scheme_redirect": status_code = codes.SEE_OTHER headers = [(b"location", b"//example.org/")] return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream( b"") elif path == b"/multiple_redirects": params = parse_qs(query.decode("ascii")) count = int(params.get("count", "0")[0]) redirect_count = count - 1 code = codes.SEE_OTHER if count else codes.OK phrase = b"See Other" if count else b"OK" location = b"/multiple_redirects" if redirect_count: location += b"?count=" + str(redirect_count).encode("ascii") headers = [(b"location", location)] if count else [] return b"HTTP/1.1", code, phrase, headers, ByteStream(b"") if path == b"/redirect_loop": code = codes.SEE_OTHER headers = [(b"location", b"/redirect_loop")] return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"") elif path == b"/cross_domain": code = codes.SEE_OTHER headers = [(b"location", b"https://example.org/cross_domain_target")] return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"") elif path == b"/cross_domain_target": headers_dict = { key.decode("ascii"): value.decode("ascii") for key, value in headers } stream = ByteStream(json.dumps({"headers": headers_dict}).encode()) return b"HTTP/1.1", 200, b"OK", [], stream elif path == b"/redirect_body": code = codes.PERMANENT_REDIRECT headers = [(b"location", b"/redirect_body_target")] return b"HTTP/1.1", code, b"Permanent Redirect", headers, ByteStream( b"") elif path == b"/redirect_no_body": code = codes.SEE_OTHER headers = [(b"location", b"/redirect_body_target")] return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"") elif path == b"/redirect_body_target": content = b"".join(stream) headers_dict = { key.decode("ascii"): value.decode("ascii") for key, value in headers } stream = ByteStream( json.dumps({ "body": content.decode(), "headers": headers_dict }).encode()) return b"HTTP/1.1", 200, b"OK", [], stream elif path == b"/cross_subdomain": host = get_header_value(headers, "host") if host != "www.example.org": headers = [(b"location", b"https://www.example.org/cross_subdomain")] return ( b"HTTP/1.1", codes.PERMANENT_REDIRECT, b"Permanent Redirect", headers, ByteStream(b""), ) else: return b"HTTP/1.1", 200, b"OK", [], ByteStream( b"Hello, world!") elif path == b"/redirect_custom_scheme": status_code = codes.MOVED_PERMANENTLY headers = [(b"location", b"market://details?id=42")] return ( b"HTTP/1.1", status_code, b"Moved Permanently", headers, ByteStream(b""), ) return b"HTTP/1.1", 200, b"OK", [], ByteStream(b"Hello, world!")
async def send( self, request: Request, verify: VerifyTypes = None, cert: CertTypes = None, timeout: TimeoutTypes = None, ) -> Response: if request.url.path == "/no_redirect": return Response(codes.OK, request=request) elif request.url.path == "/redirect_301": async def body(): yield b"<a href='https://example.org/'>here</a>" status_code = codes.MOVED_PERMANENTLY headers = {"location": "https://example.org/"} stream = AsyncIteratorStream(aiterator=body()) return Response(status_code, stream=stream, headers=headers, request=request) elif request.url.path == "/redirect_302": status_code = codes.FOUND headers = {"location": "https://example.org/"} return Response(status_code, headers=headers, request=request) elif request.url.path == "/redirect_303": status_code = codes.SEE_OTHER headers = {"location": "https://example.org/"} return Response(status_code, headers=headers, request=request) elif request.url.path == "/relative_redirect": headers = {"location": "/"} return Response(codes.SEE_OTHER, headers=headers, request=request) elif request.url.path == "/malformed_redirect": headers = {"location": "https://:443/"} return Response(codes.SEE_OTHER, headers=headers, request=request) elif request.url.path == "/no_scheme_redirect": headers = {"location": "//example.org/"} return Response(codes.SEE_OTHER, headers=headers, request=request) elif request.url.path == "/multiple_redirects": params = parse_qs(request.url.query) count = int(params.get("count", "0")[0]) redirect_count = count - 1 code = codes.SEE_OTHER if count else codes.OK location = "/multiple_redirects" if redirect_count: location += "?count=" + str(redirect_count) headers = {"location": location} if count else {} return Response(code, headers=headers, request=request) if request.url.path == "/redirect_loop": headers = {"location": "/redirect_loop"} return Response(codes.SEE_OTHER, headers=headers, request=request) elif request.url.path == "/cross_domain": headers = {"location": "https://example.org/cross_domain_target"} return Response(codes.SEE_OTHER, headers=headers, request=request) elif request.url.path == "/cross_domain_target": headers = dict(request.headers.items()) content = json.dumps({"headers": headers}).encode() return Response(codes.OK, content=content, request=request) elif request.url.path == "/redirect_body": body = b"".join([part async for part in request.stream]) headers = {"location": "/redirect_body_target"} return Response(codes.PERMANENT_REDIRECT, headers=headers, request=request) elif request.url.path == "/redirect_no_body": content = b"".join([part async for part in request.stream]) headers = {"location": "/redirect_body_target"} return Response(codes.SEE_OTHER, headers=headers, request=request) elif request.url.path == "/redirect_body_target": content = b"".join([part async for part in request.stream]) headers = dict(request.headers.items()) body = json.dumps({ "body": content.decode(), "headers": headers }).encode() return Response(codes.OK, content=body, request=request) elif request.url.path == "/cross_subdomain": if request.headers["host"] != "www.example.org": headers = { "location": "https://www.example.org/cross_subdomain" } return Response(codes.PERMANENT_REDIRECT, headers=headers, request=request) else: return Response(codes.OK, content=b"Hello, world!", request=request) return Response(codes.OK, content=b"Hello, world!", request=request)