@@ -5746,3 +5746,173 @@ async def handler(request: web.Request) -> web.Response:
57465746 data = await resp .content .read ()
57475747 assert resp .content .total_raw_bytes == len (data )
57485748 assert resp .content .total_raw_bytes == int (resp .headers ["Content-Length" ])
5749+
5750+
5751+ async def test_output_size_bytes (aiohttp_client : AiohttpClient ) -> None :
5752+ async def handler (request : web .Request ) -> web .Response :
5753+ await request .read ()
5754+ return web .Response ()
5755+
5756+ app = web .Application ()
5757+ app .router .add_post ("/" , handler )
5758+ client = await aiohttp_client (app )
5759+
5760+ body = b"x" * 1024
5761+ async with client .post ("/" , data = body ) as resp :
5762+ assert resp .output_size >= len (body )
5763+
5764+
5765+ async def test_output_size_multipart (aiohttp_client : AiohttpClient ) -> None :
5766+ async def handler (request : web .Request ) -> web .Response :
5767+ await request .read ()
5768+ return web .Response ()
5769+
5770+ app = web .Application ()
5771+ app .router .add_post ("/" , handler )
5772+ client = await aiohttp_client (app )
5773+
5774+ mpwriter = aiohttp .MultipartWriter ("form-data" )
5775+ mpwriter .append (b"x" * 4096 )
5776+ mpwriter .append (b"y" * 2048 )
5777+ expected_body_size = mpwriter .size
5778+ assert expected_body_size is not None
5779+
5780+ async with client .post ("/" , data = mpwriter ) as resp :
5781+ assert resp .output_size >= expected_body_size
5782+
5783+
5784+ async def test_output_size_keepalive_isolated (
5785+ aiohttp_client : AiohttpClient ,
5786+ ) -> None :
5787+ """Each request on a keep-alive connection has its own counter."""
5788+ transports : set [object ] = set ()
5789+
5790+ async def handler (request : web .Request ) -> web .Response :
5791+ transports .add (request .transport )
5792+ await request .read ()
5793+ return web .Response ()
5794+
5795+ app = web .Application ()
5796+ app .router .add_post ("/" , handler )
5797+ connector = aiohttp .TCPConnector (limit = 1 , force_close = False )
5798+ client = await aiohttp_client (app , connector = connector )
5799+ body = b"x" * 65536
5800+
5801+ async with client .post ("/" , data = body ) as resp1 :
5802+ size1 = resp1 .output_size
5803+
5804+ async with client .post ("/" , data = body ) as resp2 :
5805+ size2 = resp2 .output_size
5806+
5807+ assert len (transports ) == 1 # Check keep-alive worked.
5808+ assert size1 >= len (body )
5809+ assert size1 == size2
5810+
5811+
5812+ async def test_output_size_progress (aiohttp_client : AiohttpClient ) -> None :
5813+ """output_size advances by exactly one chunk per yield."""
5814+
5815+ async def handler (request : web .Request ) -> web .StreamResponse :
5816+ response = web .StreamResponse ()
5817+ await response .prepare (request )
5818+ # Flush headers + a chunk so resp.start() returns on the client
5819+ # side before we read the body.
5820+ await response .write (b"x" )
5821+ await request .read ()
5822+ return response
5823+
5824+ app = web .Application ()
5825+ app .router .add_post ("/" , handler )
5826+ client = await aiohttp_client (app )
5827+
5828+ chunk_size = 4096
5829+ chunk = b"z" * chunk_size
5830+ num_chunks = 8
5831+ sample_taken = asyncio .Event ()
5832+ next_chunk = asyncio .Event ()
5833+
5834+ async def gated_body () -> AsyncIterator [bytes ]:
5835+ for _ in range (num_chunks ):
5836+ yield chunk
5837+ sample_taken .clear ()
5838+ next_chunk .set ()
5839+ await sample_taken .wait ()
5840+
5841+ async with client .post ("/" , data = gated_body ()) as resp :
5842+ samples : list [int ] = []
5843+ for _ in range (num_chunks ):
5844+ await next_chunk .wait ()
5845+ next_chunk .clear ()
5846+ samples .append (resp .output_size )
5847+ assert not resp .upload_complete .done ()
5848+ sample_taken .set ()
5849+ await resp .upload_complete
5850+ assert resp .upload_complete .done ()
5851+ await resp .read ()
5852+
5853+ # Each sample after the first reflects exactly one more chunk on the wire.
5854+ chunked_framing = len (f"{ chunk_size :x} " .encode ()) + 4
5855+ deltas = [samples [i ] - samples [i - 1 ] for i in range (1 , len (samples ))]
5856+ assert deltas == [chunk_size + chunked_framing ] * (num_chunks - 1 )
5857+
5858+
5859+ async def test_output_size_get_request (aiohttp_client : AiohttpClient ) -> None :
5860+ """GET request with no body still reports the request header byte count."""
5861+
5862+ async def handler (request : web .Request ) -> web .Response :
5863+ return web .Response ()
5864+
5865+ app = web .Application ()
5866+ app .router .add_get ("/" , handler )
5867+ client = await aiohttp_client (app )
5868+
5869+ async with client .get ("/" ) as resp :
5870+ assert resp .output_size >= 0
5871+
5872+
5873+ async def test_output_size_writer_released (aiohttp_client : AiohttpClient ) -> None :
5874+ """Writer is dropped once body upload completes; output_size survives."""
5875+
5876+ async def handler (request : web .Request ) -> web .Response :
5877+ await request .read ()
5878+ return web .Response ()
5879+
5880+ app = web .Application ()
5881+ app .router .add_post ("/" , handler )
5882+ client = await aiohttp_client (app )
5883+
5884+ body = b"x" * 1024
5885+ async with client .post ("/" , data = body ) as resp :
5886+ await resp .read ()
5887+ assert resp ._stream_writer is None
5888+ assert resp .output_size >= len (body )
5889+
5890+
5891+ async def test_upload_complete_no_body (aiohttp_client : AiohttpClient ) -> None :
5892+ async def handler (request : web .Request ) -> web .Response :
5893+ return web .Response ()
5894+
5895+ app = web .Application ()
5896+ app .router .add_get ("/" , handler )
5897+ client = await aiohttp_client (app )
5898+
5899+ async with client .get ("/" ) as resp :
5900+ assert resp .upload_complete .done ()
5901+
5902+
5903+ async def test_upload_complete_late_access (aiohttp_client : AiohttpClient ) -> None :
5904+ """Accessing upload_complete after the upload finished returns a done future."""
5905+
5906+ async def handler (request : web .Request ) -> web .Response :
5907+ await request .read ()
5908+ return web .Response ()
5909+
5910+ app = web .Application ()
5911+ app .router .add_post ("/" , handler )
5912+ client = await aiohttp_client (app )
5913+
5914+ async with client .post ("/" , data = b"x" * 1024 ) as resp :
5915+ await resp .read ()
5916+ # Writer task is done; future is created lazily on this first access.
5917+ assert resp ._upload_complete is None
5918+ assert resp .upload_complete .done ()
0 commit comments