Class: HTTP::Protocol::HTTP2::Connection

Inherits:
Object
  • Object
show all
Includes:
FlowControl
Defined in:
lib/http/protocol/http2/connection.rb

Direct Known Subclasses

Client, Server

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from FlowControl

#available_frame_size, #consume_local_window, #consume_remote_window, #send_window_update

Constructor Details

#initialize(framer, local_stream_id) ⇒ Connection

Returns a new instance of Connection.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/http/protocol/http2/connection.rb', line 34

def initialize(framer, local_stream_id)
	@state = :new
	@streams = {}
	
	@framer = framer
	@local_stream_id = local_stream_id
	@remote_stream_id = 0
	
	@local_settings = PendingSettings.new
	@remote_settings = Settings.new
	
	@decoder = HPACK::Context.new
	@encoder = HPACK::Context.new
	
	@local_window = Window.new(@local_settings.initial_window_size)
	@remote_window = Window.new(@remote_settings.initial_window_size)
end

Instance Attribute Details

#framerObject (readonly)

Returns the value of attribute framer.



64
65
66
# File 'lib/http/protocol/http2/connection.rb', line 64

def framer
  @framer
end

#local_settingsObject

Current settings value for local and peer



70
71
72
# File 'lib/http/protocol/http2/connection.rb', line 70

def local_settings
  @local_settings
end

#local_windowObject (readonly)

Our window for receiving data. When we receive data, it reduces this window. If the window gets too small, we must send a window update.



75
76
77
# File 'lib/http/protocol/http2/connection.rb', line 75

def local_window
  @local_window
end

#remote_settingsObject

Returns the value of attribute remote_settings.



71
72
73
# File 'lib/http/protocol/http2/connection.rb', line 71

def remote_settings
  @remote_settings
end

#remote_windowObject (readonly)

Our window for sending data. When we send data, it reduces this window.



78
79
80
# File 'lib/http/protocol/http2/connection.rb', line 78

def remote_window
  @remote_window
end

#stateObject

Connection state (:new, :open, :closed).



67
68
69
# File 'lib/http/protocol/http2/connection.rb', line 67

def state
  @state
end

#streamsObject (readonly)

Returns the value of attribute streams.



103
104
105
# File 'lib/http/protocol/http2/connection.rb', line 103

def streams
  @streams
end

Instance Method Details

#closed?Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/http/protocol/http2/connection.rb', line 80

def closed?
	@state == :closed
end

#create_stream(stream_id = next_stream_id) ⇒ Object



239
240
241
# File 'lib/http/protocol/http2/connection.rb', line 239

def create_stream(stream_id = next_stream_id)
	Stream.new(self, stream_id)
end

#decode_headers(data) ⇒ Object



90
91
92
# File 'lib/http/protocol/http2/connection.rb', line 90

def decode_headers(data)
	HPACK::Decompressor.new(data, @decoder).decode
end

#encode_headers(headers, buffer = String.new.b) ⇒ Object



84
85
86
87
88
# File 'lib/http/protocol/http2/connection.rb', line 84

def encode_headers(headers, buffer = String.new.b)
	HPACK::Compressor.new(buffer, @encoder).encode(headers)
	
	return buffer
end

#idObject



52
53
54
# File 'lib/http/protocol/http2/connection.rb', line 52

def id
	0
end

#maximum_concurrent_streamsObject



60
61
62
# File 'lib/http/protocol/http2/connection.rb', line 60

def maximum_concurrent_streams
	[@local_settings.maximum_concurrent_streams, @remote_settings.maximum_concurrent_streams].min
end

#maximum_frame_sizeObject



56
57
58
# File 'lib/http/protocol/http2/connection.rb', line 56

def maximum_frame_size
	@remote_settings.maximum_frame_size
end

#next_stream_idObject

Streams are identified with an unsigned 31-bit integer. Streams initiated by a client MUST use odd-numbered stream identifiers; those initiated by the server MUST use even-numbered stream identifiers. A stream identifier of zero (0x0) is used for connection control messages; the stream identifier of zero cannot be used to establish a new stream.



95
96
97
98
99
100
101
# File 'lib/http/protocol/http2/connection.rb', line 95

def next_stream_id
	id = @local_stream_id
	
	@local_stream_id += 2
	
	return id
end

#open!Object



193
194
195
196
197
198
199
200
# File 'lib/http/protocol/http2/connection.rb', line 193

def open!
	@local_window.capacity = self.local_settings.initial_window_size
	@remote_window.capacity = self.remote_settings.initial_window_size
	
	@state = :open
	
	return self
end

#process_settings(frame) ⇒ Boolean

In addition to changing the flow-control window for streams that are not yet active, a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows (that is, streams in the “open” or “half-closed (remote)” state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value.

Returns:

  • (Boolean)

    whether the frame was an acknowledgement



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/http/protocol/http2/connection.rb', line 170

def process_settings(frame)
	if frame.acknowledgement?
		# The remote end has confirmed the settings have been received:
		changes = @local_settings.acknowledge
		
		update_local_settings(changes)
		
		return true
	else
		# The remote end is updating the settings, we reply with acknowledgement:
		reply = frame.acknowledge
		
		write_frame(reply)
		
		changes = frame.unpack
		@remote_settings.update(changes)
		
		update_remote_settings(changes)
		
		return false
	end
end

#read_frameObject



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/http/protocol/http2/connection.rb', line 105

def read_frame
	frame = @framer.read_frame
	# puts "#{self.class} #{@state} read_frame: #{frame.inspect}"
	
	yield frame if block_given?
	
	frame.apply(self)
	
	return frame
rescue ProtocolError => error
	send_goaway(error.code || PROTOCOL_ERROR, error.message)
	raise
end

#receive_data(frame) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/http/protocol/http2/connection.rb', line 225

def receive_data(frame)
	consume_local_window(frame)
	
	if stream = @streams[frame.stream_id]
		stream.receive_data(frame)
		
		if stream.closed?
			@streams.delete(stream.id)
		end
	else
		raise ProtocolError, "Bad stream"
	end
end

#receive_frame(frame) ⇒ Object



300
301
302
# File 'lib/http/protocol/http2/connection.rb', line 300

def receive_frame(frame)
	warn "Unhandled frame #{frame.inspect}"
end

#receive_headers(frame) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/http/protocol/http2/connection.rb', line 243

def receive_headers(frame)
	if stream = @streams[frame.stream_id]
		stream.receive_headers(frame)
		
		if stream.closed?
			@streams.delete(stream.id)
		end
	elsif frame.stream_id > @remote_stream_id
		if @streams.count < self.maximum_concurrent_streams
			stream = create_stream(frame.stream_id)
			stream.receive_headers(frame)
			
			@remote_stream_id = stream.id
			@streams[stream.id] = stream
		else
			raise ProtocolError, "Exceeded maximum concurrent streams"
		end
	end
end

#receive_ping(frame) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/http/protocol/http2/connection.rb', line 213

def receive_ping(frame)
	if @state != :closed
		unless frame.acknowledgement?
			reply = frame.acknowledge
			
			write_frame(reply)
		end
	else
		raise ProtocolError, "Cannot receive ping in state #{@state}"
	end
end

#receive_priority(frame) ⇒ Object



263
264
265
266
267
268
269
# File 'lib/http/protocol/http2/connection.rb', line 263

def receive_priority(frame)
	if stream = @streams[frame.stream_id]
		stream.receive_priority(frame)
	else
		raise ProtocolError, "Bad stream"
	end
end

#receive_reset_stream(frame) ⇒ Object



271
272
273
274
275
276
277
278
279
# File 'lib/http/protocol/http2/connection.rb', line 271

def receive_reset_stream(frame)
	if stream = @streams[frame.stream_id]
		stream.receive_reset_stream(frame)
		
		@streams.delete(stream.id)
	else
		raise ProtocolError, "Bad stream"
	end
end

#receive_settings(frame) ⇒ Object



202
203
204
205
206
207
208
209
210
211
# File 'lib/http/protocol/http2/connection.rb', line 202

def receive_settings(frame)
	if @state == :new
		# We transition to :open when we receive acknowledgement of first settings frame:
		open! if process_settings(frame)
	elsif @state != :closed
		process_settings(frame)
	else
		raise ProtocolError, "Cannot receive settings in state #{@state}"
	end
end

#receive_window_update(frame) ⇒ Object



281
282
283
284
285
286
287
288
289
290
291
# File 'lib/http/protocol/http2/connection.rb', line 281

def receive_window_update(frame)
	if frame.connection?
		super
	elsif stream = @streams[frame.stream_id]
		stream.receive_window_update(frame)
	elsif frame.stream_id <= @local_stream_id or frame.stream_id <= @remote_stream_id
		# The stream was closed/deleted, ignore
	else
		raise ProtocolError, "Cannot update window of non-existant stream: #{frame.stream_id}"
	end
end

#send_goaway(error_code = 0, message = "") ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/http/protocol/http2/connection.rb', line 128

def send_goaway(error_code = 0, message = "")
	frame = GoawayFrame.new
	frame.pack @last_stream_id, error_code, message
	
	write_frame(frame)
	
	@state = :closed
end

#send_ping(data) ⇒ Object



142
143
144
145
146
147
148
149
150
151
# File 'lib/http/protocol/http2/connection.rb', line 142

def send_ping(data)
	if @state != :closed
		frame = PingFrame.new
		frame.pack data
		
		write_frame(frame)
	else
		raise ProtocolError, "Cannot send ping in state #{@state}"
	end
end

#send_settings(changes) ⇒ Object



119
120
121
122
123
124
125
126
# File 'lib/http/protocol/http2/connection.rb', line 119

def send_settings(changes)
	@local_settings.append(changes)
	
	frame = SettingsFrame.new
	frame.pack(changes)
	
	write_frame(frame)
end

#update_local_settings(changes) ⇒ Object



153
154
155
156
157
158
# File 'lib/http/protocol/http2/connection.rb', line 153

def update_local_settings(changes)
	capacity = @local_settings.initial_window_size
	@streams.each_value do |stream|
		stream.local_window.capacity = capacity
	end
end

#update_remote_settings(changes) ⇒ Object



160
161
162
163
164
165
# File 'lib/http/protocol/http2/connection.rb', line 160

def update_remote_settings(changes)
	capacity = @remote_settings.initial_window_size
	@streams.each_value do |stream|
		stream.remote_window.capacity = capacity
	end
end

#window_updatedObject



293
294
295
296
297
298
# File 'lib/http/protocol/http2/connection.rb', line 293

def window_updated
	# This is very inefficient, but workable.
	@streams.each_value do |stream|
		stream.window_updated unless stream.closed?
	end
end

#write_frame(frame) ⇒ Object



137
138
139
140
# File 'lib/http/protocol/http2/connection.rb', line 137

def write_frame(frame)
	# puts "#{self.class} #{@state} write_frame: #{frame.inspect}"
	@framer.write_frame(frame)
end