File: stream.rb

package info (click to toggle)
ruby-protocol-http2 0.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 472 kB
  • sloc: ruby: 3,627; makefile: 4
file content (338 lines) | stat: -rw-r--r-- 8,935 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.

require "protocol/http2/connection_context"

describe Protocol::HTTP2::Stream do
	include_context Protocol::HTTP2::ConnectionContext
	
	def before
		client.open!
		server.open!
		
		super
	end
	
	it "can create a stream" do
		stream = client.create_stream
		expect(stream).to be_a(Protocol::HTTP2::Stream)
	end
	
	it "can create a stream with a block" do
		stream = client.create_stream do |connection, stream_id|
			expect(connection).to be_equal(client)
			expect(stream_id).to be_a(Integer)
			
			[connection, stream_id]
		end
		
		expect(stream).to be == [client, 1]
	end
	
	with "idle stream" do
		let(:stream) {client.create_stream}
		
		it "is not active" do
			expect(stream).not.to be(:active?)
		end
		
		it "can send headers" do
			expect(stream).to be(:send_headers?)
		end
		
		it "can't receive stream reset" do
			frame = Protocol::HTTP2::ResetStreamFrame.new(stream.id)
			
			expect do
				stream.receive_reset_stream(frame)
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot receive reset stream/)
		end
		
		it "can inspect the stream" do
			expect(stream.inspect).to be =~ /id=1 state=idle/
		end
		
		it "can send headers" do
			stream.send_headers([["foo", "bar"]])
			
			# Pre-create the stream in idle state:
			server_stream = server.create_stream(stream.id)
			
			expect(server_stream).to receive(:receive_headers)
			
			frame = server.read_frame
			expect(frame).to be_a(Protocol::HTTP2::HeadersFrame)
			expect(frame).not.to be(:end_stream?)
		end
		
		it "can send request and read response" do
			stream.send_headers([["foo", "bar"]], Protocol::HTTP2::END_STREAM)
			
			server_stream = server.create_stream(stream.id)
			
			expect(server_stream).to receive(:receive_headers)
			
			server.read_frame
			
			expect(server_stream.state).to be == :half_closed_remote
			
			server_stream.send_headers([[":status", "200"]])
			frame = client.read_frame
			expect(frame).to be_a(Protocol::HTTP2::HeadersFrame)
			expect(frame).not.to be(:end_stream?)
			
			server_stream.send_data("Hello World!", Protocol::HTTP2::END_STREAM)
			frame = client.read_frame
			expect(frame).to be_a(Protocol::HTTP2::DataFrame)
			expect(frame).to be(:end_stream?)
		end
		
		it "cannot send data" do
			expect do
				stream.send_data("Hello World!")
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send data in state: idle/)
		end
		
		it "cannot send reset stream" do
			expect do
				stream.send_reset_stream
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send reset stream/)
		end
		
		it "cannot receive data" do
			stream.open!
			stream.send_data("Hello World!")
			
			expect do
				server.read_frame
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot receive data/)
		end
		
		it "cannot receive stream reset" do
			stream.open!
			stream.send_reset_stream
			
			expect do
				server.read_frame
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot reset stream/)
		end
	end
	
	with "open stream" do
		let(:stream) {client.create_stream}
		let(:headers) {[[":method", "GET"], [":path", "/"], [":scheme", "https"], [":authority", "example.com"]]}
		let(:server_stream) {server[stream.id]}
		
		def before
			stream.send_headers(headers)
			server.read_frame
			
			super
		end
		
		it "is active" do
			expect(stream).to be(:active?)
		end
		
		it "can send data" do
			stream.send_data("Hello World!")
			
			expect(server_stream).to receive(:receive_data).and_return(nil)
			
			server.read_frame
		end
		
		it "can send reset stream" do
			stream.send_reset_stream
			
			expect(server_stream).to receive(:receive_reset_stream).and_return(nil)
			
			server.read_frame
		end
		
		it "can send request trailers" do
			stream.send_headers([["foo", "bar"]], Protocol::HTTP2::END_STREAM)
			
			expect(server_stream).to receive(:receive_headers)
			server.read_frame
			
			expect(server_stream.state).to be == :half_closed_remote
		end
		
		it "can send response and end stream" do
			server_stream.send_headers([["foo", "bar"]], Protocol::HTTP2::END_STREAM)
			
			expect(stream).to receive(:receive_headers)
			client.read_frame
			
			expect(stream.state).to be == :half_closed_remote
		end
		
		it "can't be opened (again)" do
			expect do
				stream.open!
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot open stream/)
		end
	end
	
	with "half closed local stream" do
		let(:stream) {client.create_stream}
		let(:headers) {[[":method", "GET"], [":path", "/"], [":scheme", "https"], [":authority", "example.com"]]}
		let(:server_stream) {server[stream.id]}
		
		def before
			stream.send_headers(headers, Protocol::HTTP2::END_STREAM)
			server.read_frame
			
			super
		end
		
		it "is active" do
			expect(stream).to be(:active?)
		end
		
		it "can send data from server side" do
			expect(server_stream.state).to be == :half_closed_remote
			
			server_stream.send_data("Hello World!", Protocol::HTTP2::END_STREAM)
			expect(server_stream).to be(:closed?)
			
			expect(stream).to receive(:receive_data).and_return(nil)
			client.read_frame
		end
		
		it "can't send data" do
			expect do
				stream.send_data("Hello World!")
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send data in state: half_closed_local/)
		end
	end
	
	with "pushed stream" do
		let(:origin_stream) {client.create_stream}
		let(:server_stream) {server[origin_stream.id]}
		
		let(:promised_stream) {@promised_stream}
		let(:stream) {client[@pushed_stream_id]}
		let(:headers) {[[":method", "GET"], [":path", "/"], [":scheme", "https"], [":authority", "example.com"]]}
		let(:response_headers) {[[":status", "200"]]}
		
		def before
			origin_stream.send_headers(headers)
			server.read_frame
			
			@promised_stream = server_stream.send_push_promise(headers)
			frame = client.read_frame
			
			@pushed_stream_id, data = frame.unpack
			
			super
		end
		
		it "can't send headers" do
			expect(stream).not.to be(:send_headers?)
			
			expect do
				stream.send_headers([["foo", "bar"]])
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send headers in state: reserved_remote/)
		end
		
		it "receives stream reset when writing headers in invalid state" do
			stream.state = :open
			
			stream.send_headers([["foo", "bar"]])
			frame = server.read_frame
			
			frame = client.read_frame
			expect(frame).to be_a(Protocol::HTTP2::ResetStreamFrame)
		end
		
		it "receives stream reset when writing data in invalid state" do
			stream.state = :open
			
			stream.send_data("Hello World!")
			frame = server.read_frame
			
			frame = client.read_frame
			expect(frame).to be_a(Protocol::HTTP2::ResetStreamFrame)
		end
		
		it "can send response and end stream" do
			promised_stream.send_headers(response_headers, Protocol::HTTP2::END_STREAM)
			expect(promised_stream.state).to be == :half_closed_remote
			
			expect(stream).to receive(:receive_headers)
			client.read_frame
			
			expect(stream.state).to be == :half_closed_local
		end
	end
	
	with "closed stream" do
		let(:stream) {client.create_stream.close!}
		
		it "is not active" do
			expect(stream).not.to be(:active?)
		end
		
		it "can't send headers" do
			expect(stream).not.to be(:send_headers?)
			
			expect do
				stream.send_headers([["foo", "bar"]])
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send headers in state: closed/)
		end
		
		it "cannot send reset stream" do
			expect do
				stream.send_reset_stream
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send reset stream/)
		end
		
		it "won't accept the same stream" do
			expect do
				client.accept_stream(stream.id)
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Invalid stream id/)
		end
		
		it "cannot reserve local" do
			expect do
				stream.reserved_local!
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot reserve stream/)
		end
		
		it "cannot reserve remote" do
			expect do
				stream.reserved_remote!
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot reserve stream/)
		end
		
		it "cannot send push promise" do
			expect do
				stream.send_push_promise([])
			end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot send push promise/)
		end
		
		it "ignores headers" do
			expect(stream).to receive(:ignore_headers)
			
			stream.receive_headers(nil)
		end
		
		it "ignores data" do
			expect(stream).to receive(:ignore_data)
			
			stream.receive_data(nil)
		end
		
		it "ignores reset stream" do
			server_stream = server.create_stream(stream.id)
			server_stream.open!
			server_stream.send_reset_stream
			
			client.read_frame
		end
	end
end