@@ -569,10 +569,14 @@ A *middleware* is a coroutine that can modify either the request or
569
569
response. For example, here's a simple *middleware * which appends
570
570
``' wink' `` to the response::
571
571
572
- from aiohttp.web import middleware
572
+ from aiohttp import web
573
+ from typing import Callable, Awaitable
573
574
574
- @middleware
575
- async def middleware(request, handler):
575
+ @web.middleware
576
+ async def middleware(
577
+ request: web.Request,
578
+ handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
579
+ ) -> web.StreamResponse:
576
580
resp = await handler(request)
577
581
resp.text = resp.text + ' wink'
578
582
return resp
@@ -614,20 +618,27 @@ post-processing like handling *CORS* and so on.
614
618
The following code demonstrates middlewares execution order::
615
619
616
620
from aiohttp import web
621
+ from typing import Callable, Awaitable
617
622
618
- async def test(request) :
623
+ async def test(request: web.Request) -> web.Response :
619
624
print('Handler function called')
620
625
return web.Response(text="Hello")
621
626
622
627
@web.middleware
623
- async def middleware1(request, handler):
628
+ async def middleware1(
629
+ request: web.Request,
630
+ handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
631
+ ) -> web.StreamResponse:
624
632
print('Middleware 1 called')
625
633
response = await handler(request)
626
634
print('Middleware 1 finished')
627
635
return response
628
636
629
637
@web.middleware
630
- async def middleware2(request, handler):
638
+ async def middleware2(
639
+ request: web.Request,
640
+ handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
641
+ ) -> web.StreamResponse:
631
642
print('Middleware 2 called')
632
643
response = await handler(request)
633
644
print('Middleware 2 finished')
@@ -646,6 +657,82 @@ Produced output::
646
657
Middleware 2 finished
647
658
Middleware 1 finished
648
659
660
+ Request Body Stream Consumption
661
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
662
+
663
+ .. warning ::
664
+
665
+ When middleware reads the request body (using :meth: `~aiohttp.web.BaseRequest.read `,
666
+ :meth: `~aiohttp.web.BaseRequest.text `, :meth: `~aiohttp.web.BaseRequest.json `, or
667
+ :meth: `~aiohttp.web.BaseRequest.post `), the body stream is consumed. However, these
668
+ high-level methods cache their result, so subsequent calls from the handler or other
669
+ middleware will return the same cached value.
670
+
671
+ The important distinction is:
672
+
673
+ - High-level methods (:meth: `~aiohttp.web.BaseRequest.read `, :meth: `~aiohttp.web.BaseRequest.text `,
674
+ :meth: `~aiohttp.web.BaseRequest.json `, :meth: `~aiohttp.web.BaseRequest.post `) cache their
675
+ results internally, so they can be called multiple times and will return the same value.
676
+ - Direct stream access via :attr: `~aiohttp.web.BaseRequest.content ` does NOT have this
677
+ caching behavior. Once you read from ``request.content `` directly (e.g., using
678
+ ``await request.content.read() ``), subsequent reads will return empty bytes.
679
+
680
+ Consider this middleware that logs request bodies::
681
+
682
+ from aiohttp import web
683
+ from typing import Callable, Awaitable
684
+
685
+ async def logging_middleware(
686
+ request: web.Request,
687
+ handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
688
+ ) -> web.StreamResponse:
689
+ # This consumes the request body stream
690
+ body = await request.text()
691
+ print(f"Request body: {body}")
692
+ return await handler(request)
693
+
694
+ async def handler(request: web.Request) -> web.Response:
695
+ # This will return the same value that was read in the middleware
696
+ # (i.e., the cached result, not an empty string)
697
+ body = await request.text()
698
+ return web.Response(text=f"Received: {body}")
699
+
700
+ In contrast, when accessing the stream directly (not recommended in middleware)::
701
+
702
+ async def stream_middleware(
703
+ request: web.Request,
704
+ handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
705
+ ) -> web.StreamResponse:
706
+ # Reading directly from the stream - this consumes it!
707
+ data = await request.content.read()
708
+ print(f"Stream data: {data}")
709
+ return await handler(request)
710
+
711
+ async def handler(request: web.Request) -> web.Response:
712
+ # This will return empty bytes because the stream was already consumed
713
+ data = await request.content.read()
714
+ # data will be b'' (empty bytes)
715
+
716
+ # However, high-level methods would still work if called for the first time:
717
+ # body = await request.text() # This would read from internal cache if available
718
+ return web.Response(text=f"Received: {data}")
719
+
720
+ When working with raw stream data that needs to be shared between middleware and handlers::
721
+
722
+ async def stream_parsing_middleware(
723
+ request: web.Request,
724
+ handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
725
+ ) -> web.StreamResponse:
726
+ # Read stream once and store the data
727
+ raw_data = await request.content.read()
728
+ request['raw_body'] = raw_data
729
+ return await handler(request)
730
+
731
+ async def handler(request: web.Request) -> web.Response:
732
+ # Access the stored data instead of reading the stream again
733
+ raw_data = request.get('raw_body', b'')
734
+ return web.Response(body=raw_data)
735
+
649
736
Example
650
737
^^^^^^^
651
738
0 commit comments