1# -*- coding: utf-8 -*- 2""" 3hyper/http20/window 4~~~~~~~~~~~~~~~~~~~ 5 6Objects that understand flow control in hyper. 7 8HTTP/2 implements connection- and stream-level flow control. This flow 9control is mandatory. Unfortunately, it's difficult for hyper to be 10all that intelligent about how it manages flow control in a general case. 11 12This module defines an interface for pluggable flow-control managers. These 13managers will define a flow-control policy. This policy will determine when to 14send WINDOWUPDATE frames. 15""" 16 17 18class BaseFlowControlManager(object): 19 """ 20 The abstract base class for flow control managers. 21 22 This class defines the interface for pluggable flow-control managers. A 23 flow-control manager defines a flow-control policy, which basically boils 24 down to deciding when to increase the flow control window. 25 26 This decision can be based on a number of factors: 27 28 - the initial window size, 29 - the size of the document being retrieved, 30 - the size of the received data frames, 31 - any other information the manager can obtain 32 33 A flow-control manager may be defined at the connection level or at the 34 stream level. If no stream-level flow-control manager is defined, an 35 instance of the connection-level flow control manager is used. 36 37 A class that inherits from this one must not adjust the member variables 38 defined in this class. They are updated and set by methods on this class. 39 """ 40 def __init__(self, initial_window_size, document_size=None): 41 #: The initial size of the connection window in bytes. This is set at 42 #: creation time. 43 self.initial_window_size = initial_window_size 44 45 #: The current size of the connection window. Any methods overridden 46 #: by the user must not adjust this value. 47 self.window_size = initial_window_size 48 49 #: The size of the document being retrieved, in bytes. This is 50 #: retrieved from the Content-Length header, if provided. Note that 51 #: the total number of bytes that will be received may be larger than 52 #: this value due to HTTP/2 padding. It should not be assumed that 53 #: simply because the the document size is smaller than the initial 54 #: window size that there will never be a need to increase the window 55 #: size. 56 self.document_size = document_size 57 58 def increase_window_size(self, frame_size): 59 """ 60 Determine whether or not to emit a WINDOWUPDATE frame. 61 62 This method should be overridden to determine, based on the state of 63 the system and the size of the received frame, whether or not a 64 WindowUpdate frame should be sent for the stream. 65 66 This method should *not* adjust any of the member variables of this 67 class. 68 69 Note that this method is called before the window size is decremented 70 as a result of the frame being handled. 71 72 :param frame_size: The size of the received frame. Note that this *may* 73 be zero. When this parameter is zero, it's possible that a 74 WINDOWUPDATE frame may want to be emitted anyway. A zero-length frame 75 size is usually associated with a change in the size of the receive 76 window due to a SETTINGS frame. 77 :returns: The amount to increase the receive window by. Return zero if 78 the window should not be increased. 79 """ 80 raise NotImplementedError( 81 "FlowControlManager is an abstract base class" 82 ) 83 84 def blocked(self): 85 """ 86 Called whenever the remote endpoint reports that it is blocked behind 87 the flow control window. 88 89 When this method is called the remote endpoint is signaling that it 90 has more data to send and that the transport layer is capable of 91 transmitting it, but that the HTTP/2 flow control window prevents it 92 being sent. 93 94 This method should return the size by which the window should be 95 incremented, which may be zero. This method should *not* adjust any 96 of the member variables of this class. 97 98 :returns: The amount to increase the receive window by. Return zero if 99 the window should not be increased. 100 """ 101 # TODO: Is this method necessary? 102 raise NotImplementedError( 103 "FlowControlManager is an abstract base class" 104 ) 105 106 def _handle_frame(self, frame_size): 107 """ 108 This internal method is called by the connection or stream that owns 109 the flow control manager. It handles the generic behaviour of flow 110 control managers: namely, keeping track of the window size. 111 """ 112 rc = self.increase_window_size(frame_size) 113 self.window_size -= frame_size 114 self.window_size += rc 115 return rc 116 117 def _blocked(self): 118 """ 119 This internal method is called by the connection or stream that owns 120 the flow control manager. It handles the generic behaviour of receiving 121 BLOCKED frames. 122 """ 123 rc = self.blocked() 124 self.window_size += rc 125 return rc 126 127 128class FlowControlManager(BaseFlowControlManager): 129 """ 130 ``hyper``'s default flow control manager. 131 132 This implements hyper's flow control algorithms. This algorithm attempts to 133 reduce the number of WINDOWUPDATE frames we send without blocking the 134 remote endpoint behind the flow control window. 135 136 This algorithm will become more complicated over time. In the current form, 137 the algorithm is very simple: 138 139 - When the flow control window gets less than 1/4 of the maximum size, 140 increment back to the maximum. 141 - Otherwise, if the flow control window gets to less than 1kB, increment 142 back to the maximum. 143 """ 144 def increase_window_size(self, frame_size): 145 future_window_size = self.window_size - frame_size 146 147 if ((future_window_size < (self.initial_window_size / 4)) or 148 (future_window_size < 1000)): 149 return self.initial_window_size - future_window_size 150 151 return 0 152 153 def blocked(self): 154 return self.initial_window_size - self.window_size 155