88This example demonstrates handling requests with bodies, as well as handling
99those without. In particular, it demonstrates the fact that DataReceived may
1010be called multiple times, and that applications must handle that possibility.
11-
12- Please note that this example does not handle flow control, and so only works
13- properly for relatively small requests. Please see other examples to understand
14- how flow control should work.
1511"""
1612import asyncio
1713import io
2319from h2 .config import H2Configuration
2420from h2 .connection import H2Connection
2521from h2 .events import (
26- ConnectionTerminated , DataReceived , RequestReceived , StreamEnded
22+ ConnectionTerminated , DataReceived , RequestReceived , StreamEnded ,
23+ StreamReset
2724)
2825from h2 .errors import ErrorCodes
29- from h2 .exceptions import ProtocolError
26+ from h2 .exceptions import ProtocolError , StreamClosedError
3027
3128
3229RequestData = collections .namedtuple ('RequestData' , ['headers' , 'data' ])
@@ -38,12 +35,18 @@ def __init__(self):
3835 self .conn = H2Connection (config = config )
3936 self .transport = None
4037 self .stream_data = {}
38+ self .flow_control_futures = {}
4139
4240 def connection_made (self , transport : asyncio .Transport ):
4341 self .transport = transport
4442 self .conn .initiate_connection ()
4543 self .transport .write (self .conn .data_to_send ())
4644
45+ def connection_lost (self , exc ):
46+ for future in self .flow_control_futures .values ():
47+ future .cancel ()
48+ self .flow_control_futures = {}
49+
4750 def data_received (self , data : bytes ):
4851 try :
4952 events = self .conn .receive_data (data )
@@ -61,18 +64,15 @@ def data_received(self, data: bytes):
6164 self .stream_complete (event .stream_id )
6265 elif isinstance (event , ConnectionTerminated ):
6366 self .transport .close ()
67+ elif isinstance (event , StreamReset ):
68+ self .stream_reset (event .stream_id )
6469
6570 self .transport .write (self .conn .data_to_send ())
6671
6772 def request_received (self , headers : List [Tuple [str , str ]], stream_id : int ):
6873 headers = collections .OrderedDict (headers )
6974 method = headers [':method' ]
7075
71- # We only support GET and POST.
72- if method not in ('GET' , 'POST' ):
73- self .return_405 (headers , stream_id )
74- return
75-
7676 # Store off the request data.
7777 request_data = RequestData (headers , io .BytesIO ())
7878 self .stream_data [stream_id ] = request_data
@@ -101,18 +101,7 @@ def stream_complete(self, stream_id: int):
101101 ('server' , 'asyncio-h2' ),
102102 )
103103 self .conn .send_headers (stream_id , response_headers )
104- self .conn .send_data (stream_id , data , end_stream = True )
105-
106- def return_405 (self , headers : List [Tuple [str , str ]], stream_id : int ):
107- """
108- We don't support the given method, so we want to return a 405 response.
109- """
110- response_headers = (
111- (':status' , '405' ),
112- ('content-length' , '0' ),
113- ('server' , 'asyncio-h2' ),
114- )
115- self .conn .send_headers (stream_id , response_headers , end_stream = True )
104+ asyncio .ensure_future (self .send_data (data , stream_id ))
116105
117106 def receive_data (self , data : bytes , stream_id : int ):
118107 """
@@ -128,6 +117,67 @@ def receive_data(self, data: bytes, stream_id: int):
128117 else :
129118 stream_data .data .write (data )
130119
120+ def stream_reset (self , stream_id ):
121+ """
122+ A stream reset was sent. Stop sending data.
123+ """
124+ if stream_id in self .flow_control_futures :
125+ future = self .flow_control_futures .pop (stream_id )
126+ future .cancel ()
127+
128+ async def send_data (self , data , stream_id ):
129+ """
130+ Send data according to the flow control rules.
131+ """
132+ while data :
133+ while not self .conn .local_flow_control_window (stream_id ):
134+ try :
135+ await self .wait_for_flow_control (stream_id )
136+ except asyncio .CancelledError :
137+ return
138+
139+ chunk_size = min (
140+ self .conn .local_flow_control_window (stream_id ),
141+ len (data ),
142+ self .conn .max_outbound_frame_size ,
143+ )
144+
145+ try :
146+ self .conn .send_data (
147+ stream_id ,
148+ data [:chunk_size ],
149+ end_stream = (chunk_size == len (data ))
150+ )
151+ except (StreamClosedError , ProtocolError ):
152+ # The stream got closed and we didn't get told. We're done
153+ # here.
154+ break
155+
156+ self .transport .write (self .conn .data_to_send ())
157+ data = data [chunk_size :]
158+
159+ async def wait_for_flow_control (self , stream_id ):
160+ """
161+ Waits for a Future that fires when the flow control window is opened.
162+ """
163+ f = asyncio .Future ()
164+ self .flow_control_futures [stream_id ] = f
165+ await f
166+
167+ def window_updated (self , stream_id , delta ):
168+ """
169+ A window update frame was received. Unblock some number of flow control
170+ Futures.
171+ """
172+ if stream_id and stream_id in self .flow_control_futures :
173+ f = self .flow_control_futures .pop (stream_id )
174+ f .set_result (delta )
175+ elif not stream_id :
176+ for f in self .flow_control_futures .values ():
177+ f .set_result (delta )
178+
179+ self .flow_control_futures = {}
180+
131181
132182ssl_context = ssl .create_default_context (ssl .Purpose .CLIENT_AUTH )
133183ssl_context .options |= (
0 commit comments