biryani 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/README.md +7 -2
- data/example/echo.rb +2 -2
- data/example/hello_world.rb +2 -2
- data/lib/biryani/connection.rb +91 -96
- data/lib/biryani/data_buffer.rb +26 -16
- data/lib/biryani/frame/headers.rb +0 -2
- data/lib/biryani/http_response.rb +4 -20
- data/lib/biryani/state.rb +14 -2
- data/lib/biryani/streams_context.rb +60 -5
- data/lib/biryani/utils.rb +8 -0
- data/lib/biryani/version.rb +1 -1
- data/lib/biryani/window.rb +11 -13
- data/lib/biryani.rb +3 -2
- data/spec/connection/handle_connection_window_update_spec.rb +1 -1
- data/spec/connection/handle_rst_stream_spec.rb +2 -2
- data/spec/connection/handle_settings_spec.rb +8 -2
- data/spec/connection/handle_stream_window_update_spec.rb +2 -2
- data/spec/connection/send_spec.rb +34 -44
- data/spec/connection/transition_state_send_spec.rb +3 -3
- data/spec/data_buffer_spec.rb +43 -43
- data/spec/streams_context_spec.rb +79 -0
- metadata +4 -5
- data/spec/connection/close_all_streams_spec.rb +0 -17
- data/spec/connection/remove_closed_streams_spec.rb +0 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e8b17b7b795ee787a07fe5be9e453d4486fdce914609539446932e4fb988912
|
|
4
|
+
data.tar.gz: 45f9196ca41aecf8efecaee4e29e459e20a6ac05116b8e384858f52d365c4726
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13ba324d9792cde457634d20d72bb7f276772aa6b1eacd878dd82cd3f2fade7f5025472e7bf4b057208f71b68bf8f6df7e2f07bfaeda62baa0c9c6f7c5f58018
|
|
7
|
+
data.tar.gz: ed9846fa55648102b94242338429b45d6bea100eb1427a736c5a90886c6e3933602138407f0bd474f5c3f024887100f2fc241b46c16bfa2f035ca0ce08f8c1f2
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -32,8 +32,8 @@ port = ARGV[0] || 8888
|
|
|
32
32
|
socket = TCPServer.new(port)
|
|
33
33
|
|
|
34
34
|
server = Biryani::Server.new(
|
|
35
|
-
# @
|
|
36
|
-
# @
|
|
35
|
+
# @param _req [Biryani::HTTPRequest]
|
|
36
|
+
# @param res [Biryani::HTTPResponse]
|
|
37
37
|
Ractor.shareable_proc do |_req, res|
|
|
38
38
|
res.status = 200
|
|
39
39
|
res.content = 'Hello, world!'
|
|
@@ -42,6 +42,11 @@ server = Biryani::Server.new(
|
|
|
42
42
|
server.run(socket)
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
```sh-session
|
|
46
|
+
$ curl --http2-prior-knowledge http://localhost:8888
|
|
47
|
+
Hello, world!
|
|
48
|
+
```
|
|
49
|
+
|
|
45
50
|
|
|
46
51
|
## License
|
|
47
52
|
|
data/example/echo.rb
CHANGED
|
@@ -9,8 +9,8 @@ port = ARGV[0] || 8888
|
|
|
9
9
|
socket = TCPServer.new(port)
|
|
10
10
|
|
|
11
11
|
server = Biryani::Server.new(
|
|
12
|
-
# @
|
|
13
|
-
# @
|
|
12
|
+
# @param req [Biryani::HTTPRequest]
|
|
13
|
+
# @param res [Biryani::HTTPResponse]
|
|
14
14
|
Ractor.shareable_proc do |req, res|
|
|
15
15
|
res.status = 200
|
|
16
16
|
|
data/example/hello_world.rb
CHANGED
|
@@ -9,8 +9,8 @@ port = ARGV[0] || 8888
|
|
|
9
9
|
socket = TCPServer.new(port)
|
|
10
10
|
|
|
11
11
|
server = Biryani::Server.new(
|
|
12
|
-
# @
|
|
13
|
-
# @
|
|
12
|
+
# @param _req [Biryani::HTTPRequest]
|
|
13
|
+
# @param res [Biryani::HTTPResponse]
|
|
14
14
|
Ractor.shareable_proc do |_req, res|
|
|
15
15
|
res.status = 200
|
|
16
16
|
res.content = 'Hello, world!'
|
data/lib/biryani/connection.rb
CHANGED
|
@@ -24,11 +24,11 @@ module Biryani
|
|
|
24
24
|
@streams_ctx = StreamsContext.new
|
|
25
25
|
@encoder = HPACK::Encoder.new(4_096)
|
|
26
26
|
@decoder = HPACK::Decoder.new(4_096)
|
|
27
|
-
@send_window = Window.new
|
|
28
|
-
@recv_window = Window.new
|
|
27
|
+
@send_window = Window.new(65_535)
|
|
28
|
+
@recv_window = Window.new(65_535)
|
|
29
29
|
@data_buffer = DataBuffer.new
|
|
30
|
-
@
|
|
31
|
-
@
|
|
30
|
+
@settings = self.class.default_settings # Hash<Integer, Integer>
|
|
31
|
+
@peer_settings = self.class.default_settings # Hash<Integer, Integer>
|
|
32
32
|
@closed = false
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -66,6 +66,7 @@ module Biryani
|
|
|
66
66
|
# rubocop: disable Metrics/AbcSize
|
|
67
67
|
# rubocop: disable Metrics/BlockLength
|
|
68
68
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
69
|
+
# rubocop: disable Metrics/MethodLength
|
|
69
70
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
70
71
|
def send_loop(io)
|
|
71
72
|
loop do
|
|
@@ -75,11 +76,11 @@ module Biryani
|
|
|
75
76
|
|
|
76
77
|
port, obj = Ractor.select(*ports)
|
|
77
78
|
if port == @sock
|
|
78
|
-
if
|
|
79
|
+
if Biryani.err?(obj)
|
|
79
80
|
reply_frame = self.class.unwrap(obj, @streams_ctx.last_stream_id)
|
|
80
81
|
self.class.do_send(io, reply_frame, true)
|
|
81
82
|
close if self.class.transition_state_send(reply_frame, @streams_ctx)
|
|
82
|
-
elsif obj.length > @
|
|
83
|
+
elsif obj.length > @settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
83
84
|
self.class.do_send(io, Frame::Goaway.new(0, @streams_ctx.last_stream_id, ErrorCode::FRAME_SIZE_ERROR, 'payload length greater than SETTINGS_MAX_FRAME_SIZE'), true)
|
|
84
85
|
close
|
|
85
86
|
else
|
|
@@ -90,21 +91,24 @@ module Biryani
|
|
|
90
91
|
end
|
|
91
92
|
end
|
|
92
93
|
else
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
res, stream_id = obj
|
|
95
|
+
fragment, data = self.class.http_response(res, @encoder)
|
|
96
|
+
max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
97
|
+
self.class.send_headers(io, stream_id, fragment, data.empty?, max_frame_size, @streams_ctx)
|
|
98
|
+
self.class.send_data(io, stream_id, data, @send_window, max_frame_size, @streams_ctx, @data_buffer) unless data.empty?
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
@streams_ctx.remove_closed(@data_buffer)
|
|
98
101
|
end
|
|
99
102
|
|
|
100
103
|
break if closed?
|
|
101
104
|
end
|
|
102
105
|
ensure
|
|
103
|
-
|
|
106
|
+
@streams_ctx.clear_all
|
|
104
107
|
end
|
|
105
108
|
# rubocop: enable Metrics/AbcSize
|
|
106
109
|
# rubocop: enable Metrics/BlockLength
|
|
107
110
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
111
|
+
# rubocop: enable Metrics/MethodLength
|
|
108
112
|
# rubocop: enable Metrics/PerceivedComplexity
|
|
109
113
|
|
|
110
114
|
# @param frame [Object]
|
|
@@ -128,7 +132,7 @@ module Biryani
|
|
|
128
132
|
when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
|
|
129
133
|
[ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
|
|
130
134
|
when FrameType::SETTINGS
|
|
131
|
-
obj = self.class.handle_settings(frame, @
|
|
135
|
+
obj = self.class.handle_settings(frame, @peer_settings, @decoder, @streams_ctx)
|
|
132
136
|
return [] if obj.nil?
|
|
133
137
|
|
|
134
138
|
settings_ack = obj
|
|
@@ -137,7 +141,8 @@ module Biryani
|
|
|
137
141
|
obj = self.class.handle_ping(frame)
|
|
138
142
|
return [] if obj.nil?
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
ping_ack = obj
|
|
145
|
+
[ping_ack]
|
|
141
146
|
when FrameType::GOAWAY
|
|
142
147
|
self.class.handle_goaway(frame)
|
|
143
148
|
# TODO: logging error
|
|
@@ -146,7 +151,8 @@ module Biryani
|
|
|
146
151
|
err = self.class.handle_connection_window_update(frame, @send_window)
|
|
147
152
|
return [err] unless err.nil?
|
|
148
153
|
|
|
149
|
-
|
|
154
|
+
max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
155
|
+
@data_buffer.take!(@send_window, @streams_ctx, max_frame_size)
|
|
150
156
|
else
|
|
151
157
|
# ignore unknown frame type
|
|
152
158
|
[]
|
|
@@ -167,16 +173,20 @@ module Biryani
|
|
|
167
173
|
return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
|
|
168
174
|
if [FrameType::SETTINGS, FrameType::PING, FrameType::GOAWAY].include?(typ)
|
|
169
175
|
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
max_streams = @peer_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS]
|
|
177
|
+
send_initial_window_size = @peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
178
|
+
recv_initial_window_size = @settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
179
|
+
obj = self.class.transition_state_recv(frame, @streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size, @proc)
|
|
180
|
+
return [obj] if Biryani.err?(obj)
|
|
172
181
|
|
|
173
182
|
ctx = obj
|
|
174
183
|
case typ
|
|
175
184
|
when FrameType::DATA
|
|
185
|
+
# TODO: flow-control using @recv_window & ctx.recv_window
|
|
176
186
|
ctx.content << frame.data
|
|
177
187
|
if ctx.state.half_closed_remote?
|
|
178
188
|
obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
|
|
179
|
-
return [obj] if
|
|
189
|
+
return [obj] if Biryani.err?(obj)
|
|
180
190
|
|
|
181
191
|
ctx.stream.rx << obj
|
|
182
192
|
end
|
|
@@ -186,7 +196,7 @@ module Biryani
|
|
|
186
196
|
ctx.fragment << frame.fragment
|
|
187
197
|
if ctx.state.half_closed_remote?
|
|
188
198
|
obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
|
|
189
|
-
return [obj] if
|
|
199
|
+
return [obj] if Biryani.err?(obj)
|
|
190
200
|
|
|
191
201
|
ctx.stream.rx << obj
|
|
192
202
|
end
|
|
@@ -205,7 +215,8 @@ module Biryani
|
|
|
205
215
|
err = self.class.handle_stream_window_update(frame, @streams_ctx)
|
|
206
216
|
return [err] unless err.nil?
|
|
207
217
|
|
|
208
|
-
|
|
218
|
+
max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
219
|
+
@data_buffer.take!(@send_window, @streams_ctx, max_frame_size)
|
|
209
220
|
else
|
|
210
221
|
# ignore UNKNOWN Frame
|
|
211
222
|
[]
|
|
@@ -244,20 +255,22 @@ module Biryani
|
|
|
244
255
|
# @param streams_ctx [StreamsContext]
|
|
245
256
|
# @param stream_id [Integer]
|
|
246
257
|
# @param max_streams [Integer]
|
|
258
|
+
# @param send_initial_window_size [Integer]
|
|
259
|
+
# @param recv_initial_window_size [Integer]
|
|
247
260
|
# @param proc [Proc]
|
|
248
261
|
#
|
|
249
262
|
# @return [StreamContext, StreamError, ConnectionError]
|
|
250
263
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
251
264
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
252
|
-
def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, proc)
|
|
265
|
+
def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size, proc)
|
|
253
266
|
ctx = streams_ctx[stream_id]
|
|
254
267
|
return StreamError.new(ErrorCode::PROTOCOL_ERROR, stream_id, 'exceed max concurrent streams') if ctx.nil? && streams_ctx.count_active + 1 > max_streams
|
|
255
268
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'even-numbered stream identifier') if ctx.nil? && stream_id.even?
|
|
256
269
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'new stream identifier is less than the existing stream identifiers') if ctx.nil? && streams_ctx.last_stream_id > stream_id
|
|
257
270
|
|
|
258
|
-
ctx = streams_ctx.new_context(stream_id, proc) if ctx.nil?
|
|
271
|
+
ctx = streams_ctx.new_context(stream_id, send_initial_window_size, recv_initial_window_size, proc) if ctx.nil?
|
|
259
272
|
obj = ctx.state.transition!(recv_frame, :recv)
|
|
260
|
-
return obj if
|
|
273
|
+
return obj if Biryani.err?(obj)
|
|
261
274
|
|
|
262
275
|
ctx
|
|
263
276
|
end
|
|
@@ -275,7 +288,7 @@ module Biryani
|
|
|
275
288
|
when FrameType::SETTINGS, FrameType::PING
|
|
276
289
|
false
|
|
277
290
|
when FrameType::GOAWAY
|
|
278
|
-
|
|
291
|
+
streams_ctx.close_all
|
|
279
292
|
true
|
|
280
293
|
else
|
|
281
294
|
streams_ctx[stream_id].state.transition!(send_frame, :send)
|
|
@@ -283,38 +296,6 @@ module Biryani
|
|
|
283
296
|
end
|
|
284
297
|
end
|
|
285
298
|
|
|
286
|
-
# @param streams_ctx [StreamsContext]
|
|
287
|
-
def self.clear_all_streams(streams_ctx)
|
|
288
|
-
streams_ctx.each do |ctx|
|
|
289
|
-
ctx.tx.close
|
|
290
|
-
ctx.stream.rx << nil
|
|
291
|
-
ctx.fragment.close
|
|
292
|
-
ctx.content.close
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# @param streams_ctx [StreamsContext]
|
|
297
|
-
def self.close_all_streams(streams_ctx)
|
|
298
|
-
streams_ctx.each do |ctx|
|
|
299
|
-
ctx.tx.close
|
|
300
|
-
ctx.fragment.close
|
|
301
|
-
ctx.content.close
|
|
302
|
-
ctx.state.close
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
# @param streams_ctx [StreamsContext]
|
|
307
|
-
# @param data_buffer [DataBuffer]
|
|
308
|
-
def self.remove_closed_streams(streams_ctx, data_buffer)
|
|
309
|
-
closed_ids = streams_ctx.closed_stream_ids.filter { |id| !data_buffer.has?(id) }
|
|
310
|
-
closed_ids.each do |id|
|
|
311
|
-
streams_ctx[id].tx.close
|
|
312
|
-
streams_ctx[id].stream.rx << nil
|
|
313
|
-
streams_ctx[id].fragment.close
|
|
314
|
-
streams_ctx[id].content.close
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
|
|
318
299
|
# @param io [IO]
|
|
319
300
|
# @param frame [Object]
|
|
320
301
|
# @param flush [Boolean]
|
|
@@ -324,39 +305,46 @@ module Biryani
|
|
|
324
305
|
end
|
|
325
306
|
|
|
326
307
|
# @param io [IO]
|
|
327
|
-
# @param
|
|
308
|
+
# @param stream_id [Integer]
|
|
309
|
+
# @param data [String]
|
|
328
310
|
# @param send_window [Window]
|
|
311
|
+
# @param max_frame_size [Integer]
|
|
329
312
|
# @param streams_ctx [StreamsContext]
|
|
330
313
|
# @param data_buffer [DataBuffer]
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def self.send(io, frame, send_window, streams_ctx, data_buffer)
|
|
334
|
-
if frame.f_type != FrameType::DATA
|
|
335
|
-
do_send(io, frame, false)
|
|
336
|
-
return transition_state_send(frame, streams_ctx)
|
|
337
|
-
end
|
|
314
|
+
def self.send_data(io, stream_id, data, send_window, max_frame_size, streams_ctx, data_buffer)
|
|
315
|
+
frames, remains = streams_ctx.sendable_data_frames(stream_id, data, send_window, max_frame_size)
|
|
338
316
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
send_window.consume!(
|
|
343
|
-
streams_ctx
|
|
344
|
-
return transition_state_send(frame, streams_ctx)
|
|
317
|
+
frames.each do |frame|
|
|
318
|
+
do_send(io, frame, false)
|
|
319
|
+
send_window.consume!(frame.length)
|
|
320
|
+
streams_ctx[stream_id].send_window.consume!(frame.length)
|
|
321
|
+
transition_state_send(frame, streams_ctx)
|
|
345
322
|
end
|
|
346
323
|
|
|
347
|
-
data_buffer
|
|
348
|
-
false
|
|
324
|
+
data_buffer.store(stream_id, remains) unless remains.empty?
|
|
349
325
|
end
|
|
350
326
|
|
|
351
|
-
# @param
|
|
352
|
-
# @param
|
|
327
|
+
# @param io [IO]
|
|
328
|
+
# @param stream_id [Integer]
|
|
329
|
+
# @param fragment [String]
|
|
330
|
+
# @param only_headers [Boolean]
|
|
331
|
+
# @param max_frame_size [Integer]
|
|
353
332
|
# @param streams_ctx [StreamsContext]
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
333
|
+
def self.send_headers(io, stream_id, fragment, only_headers, max_frame_size, streams_ctx)
|
|
334
|
+
len = (fragment.bytesize + max_frame_size - 1) / max_frame_size
|
|
335
|
+
frames = fragment.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
|
|
336
|
+
end_headers = index == len - 1
|
|
337
|
+
if index.zero?
|
|
338
|
+
Frame::Headers.new(end_headers, only_headers, stream_id, nil, nil, s, nil)
|
|
339
|
+
else
|
|
340
|
+
Frame::Continuation.new(end_headers, stream_id, s)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
frames.each do |frame|
|
|
345
|
+
do_send(io, frame, false)
|
|
346
|
+
transition_state_send(frame, streams_ctx)
|
|
347
|
+
end
|
|
360
348
|
end
|
|
361
349
|
|
|
362
350
|
# @param io [IO]
|
|
@@ -375,21 +363,28 @@ module Biryani
|
|
|
375
363
|
end
|
|
376
364
|
|
|
377
365
|
# @param settings [Settings]
|
|
378
|
-
# @param
|
|
366
|
+
# @param peer_settings [Hash<Integer, Integer>]
|
|
379
367
|
# @param decoder [Decoder]
|
|
368
|
+
# @param streams_ctx [StreamsContext]
|
|
380
369
|
#
|
|
381
370
|
# @return [Settings]
|
|
382
|
-
def self.handle_settings(settings,
|
|
371
|
+
def self.handle_settings(settings, peer_settings, decoder, streams_ctx)
|
|
383
372
|
return nil if settings.ack?
|
|
384
373
|
|
|
385
|
-
|
|
386
|
-
|
|
374
|
+
peer_settings.merge!(settings.setting)
|
|
375
|
+
new_limit = peer_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE]
|
|
376
|
+
decoder.limit!(new_limit)
|
|
377
|
+
send_initial_window_size = peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
378
|
+
streams_ctx.each do |ctx|
|
|
379
|
+
ctx.send_window.update!(send_initial_window_size)
|
|
380
|
+
end
|
|
381
|
+
|
|
387
382
|
Frame::Settings.new(true, 0, {})
|
|
388
383
|
end
|
|
389
384
|
|
|
390
385
|
# @param ping [Ping]
|
|
391
386
|
#
|
|
392
|
-
# @return [Ping, nil
|
|
387
|
+
# @return [Ping, nil]
|
|
393
388
|
def self.handle_ping(ping)
|
|
394
389
|
Frame::Ping.new(true, 0, ping.opaque) unless ping.ack?
|
|
395
390
|
end
|
|
@@ -402,21 +397,22 @@ module Biryani
|
|
|
402
397
|
#
|
|
403
398
|
# @return [nil, ConnectionError]
|
|
404
399
|
def self.handle_connection_window_update(window_update, send_window)
|
|
405
|
-
# TODO: send WINDOW_UPDATE
|
|
400
|
+
# TODO: send WINDOW_UPDATE to do the flow-conrol
|
|
406
401
|
send_window.increase!(window_update.window_size_increment)
|
|
402
|
+
return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'flow-control window exceeds 2^31-1') if send_window.length > 2**31 - 1
|
|
403
|
+
|
|
407
404
|
nil
|
|
408
405
|
end
|
|
409
406
|
|
|
410
407
|
# @param window_update [WindowUpdate]
|
|
411
408
|
# @param streams_ctx [StreamsContext]
|
|
412
409
|
#
|
|
413
|
-
# @return [nil,
|
|
410
|
+
# @return [nil, StreamError]
|
|
414
411
|
def self.handle_stream_window_update(window_update, streams_ctx)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
412
|
+
stream_id = window_update.stream_id
|
|
413
|
+
streams_ctx[stream_id].send_window.increase!(window_update.window_size_increment)
|
|
414
|
+
return StreamError.new(ErrorCode::FLOW_CONTROL_ERROR, stream_id, 'flow-control window exceeds 2^31-1') if streams_ctx[stream_id].send_window.length > 2**31 - 1
|
|
418
415
|
|
|
419
|
-
streams_ctx[window_update.stream_id].send_window.increase!(window_update.window_size_increment)
|
|
420
416
|
nil
|
|
421
417
|
end
|
|
422
418
|
|
|
@@ -427,7 +423,7 @@ module Biryani
|
|
|
427
423
|
# @return [HTTPRequest, ConnectionError]
|
|
428
424
|
def self.http_request(fragment, content, decoder)
|
|
429
425
|
obj = decoder.decode(fragment)
|
|
430
|
-
return obj if
|
|
426
|
+
return obj if Biryani.err?(obj)
|
|
431
427
|
|
|
432
428
|
fields = obj
|
|
433
429
|
builder = HTTPRequestBuilder.new
|
|
@@ -438,13 +434,12 @@ module Biryani
|
|
|
438
434
|
end
|
|
439
435
|
|
|
440
436
|
# @param res [HTTPResponse]
|
|
441
|
-
# @param stream_id [Integer]
|
|
442
437
|
# @param encoder [Encoder]
|
|
443
|
-
# @param max_frame_size [Integer]
|
|
444
438
|
#
|
|
445
|
-
# @return [
|
|
446
|
-
|
|
447
|
-
|
|
439
|
+
# @return [String] fragment
|
|
440
|
+
# @return [String] data
|
|
441
|
+
def self.http_response(res, encoder)
|
|
442
|
+
HTTPResponseParser.new(res).parse(encoder)
|
|
448
443
|
end
|
|
449
444
|
|
|
450
445
|
# @return [Hash<Integer, Integer>]
|
data/lib/biryani/data_buffer.rb
CHANGED
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
module Biryani
|
|
2
2
|
class DataBuffer
|
|
3
3
|
def initialize
|
|
4
|
-
@buffer =
|
|
4
|
+
@buffer = {} # Hash<Integer, String>
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
# @param
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# @param stream_id [Integer]
|
|
8
|
+
# @param data [String]
|
|
9
|
+
def store(stream_id, data)
|
|
10
|
+
@buffer[stream_id] = '' unless @buffer.key?(stream_id)
|
|
11
|
+
@buffer[stream_id] += data
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
# @param send_window [Window]
|
|
13
|
-
# @param
|
|
15
|
+
# @param streams_ctx [StreamsContext]
|
|
16
|
+
# @param max_frame_size [Intger]
|
|
14
17
|
#
|
|
15
|
-
# @return [Array<
|
|
16
|
-
def take!(send_window,
|
|
17
|
-
datas =
|
|
18
|
-
@buffer.
|
|
19
|
-
|
|
18
|
+
# @return [Array<Object>] frames
|
|
19
|
+
def take!(send_window, streams_ctx, max_frame_size)
|
|
20
|
+
datas = []
|
|
21
|
+
@buffer.each do |stream_id, data|
|
|
22
|
+
frames, remains = streams_ctx.sendable_data_frames(stream_id, data, send_window, max_frame_size)
|
|
23
|
+
next if frames.empty?
|
|
24
|
+
|
|
25
|
+
datas += frames
|
|
26
|
+
if remains.empty?
|
|
27
|
+
@buffer.delete(stream_id)
|
|
28
|
+
else
|
|
29
|
+
@buffer[stream_id] = remains
|
|
30
|
+
end
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
len = frames.map(&:length).sum
|
|
33
|
+
send_window.consume!(len)
|
|
34
|
+
streams_ctx[stream_id].send_window.consume!(len)
|
|
24
35
|
end
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
datas.values
|
|
37
|
+
datas
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
# @return [Integer]
|
|
@@ -36,7 +46,7 @@ module Biryani
|
|
|
36
46
|
#
|
|
37
47
|
# @return [Boolean]
|
|
38
48
|
def has?(stream_id)
|
|
39
|
-
@buffer.
|
|
49
|
+
@buffer.key?(stream_id)
|
|
40
50
|
end
|
|
41
51
|
end
|
|
42
52
|
end
|
|
@@ -10,7 +10,6 @@ module Biryani
|
|
|
10
10
|
# @param weight [Integer, nil]
|
|
11
11
|
# @param fragment [String]
|
|
12
12
|
# @param padding [String, nil]
|
|
13
|
-
# rubocop: disable Metrics/ParameterLists
|
|
14
13
|
def initialize(end_headers, end_stream, stream_id, stream_dependency, weight, fragment, padding)
|
|
15
14
|
@f_type = FrameType::HEADERS
|
|
16
15
|
@end_headers = end_headers
|
|
@@ -21,7 +20,6 @@ module Biryani
|
|
|
21
20
|
@fragment = fragment
|
|
22
21
|
@padding = padding
|
|
23
22
|
end
|
|
24
|
-
# rubocop: enable Metrics/ParameterLists
|
|
25
23
|
|
|
26
24
|
# @return [Boolean]
|
|
27
25
|
def priority?
|
|
@@ -34,28 +34,12 @@ module Biryani
|
|
|
34
34
|
@res.content
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# @param stream_id [Integer]
|
|
38
37
|
# @param encoder [Encoder]
|
|
39
|
-
# @param max_frame_size [Integer]
|
|
40
38
|
#
|
|
41
|
-
# @return [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
frames = fragment.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
|
|
46
|
-
if index.zero?
|
|
47
|
-
Frame::Headers.new(len < 2, content.empty?, stream_id, nil, nil, s, nil)
|
|
48
|
-
else
|
|
49
|
-
Frame::Continuation.new(index == len - 1, stream_id, s)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
len = (content.bytesize + max_frame_size - 1) / max_frame_size
|
|
54
|
-
frames += content.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
|
|
55
|
-
Frame::Data.new(index == len - 1, stream_id, s, nil)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
frames
|
|
39
|
+
# @return [String] fragment
|
|
40
|
+
# @return [String] data
|
|
41
|
+
def parse(encoder)
|
|
42
|
+
[encoder.encode(fields), content]
|
|
59
43
|
end
|
|
60
44
|
end
|
|
61
45
|
end
|
data/lib/biryani/state.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Biryani
|
|
|
8
8
|
# @param direction [:send, :recv]
|
|
9
9
|
def transition!(frame, direction)
|
|
10
10
|
obj = self.class.next(@state, frame, direction)
|
|
11
|
-
return obj if
|
|
11
|
+
return obj if Biryani.err?(obj)
|
|
12
12
|
|
|
13
13
|
@state = obj
|
|
14
14
|
end
|
|
@@ -76,6 +76,8 @@ module Biryani
|
|
|
76
76
|
# receiving_continuation_data
|
|
77
77
|
in [:receiving_continuation_data, FrameType::RST_STREAM, _]
|
|
78
78
|
:closed
|
|
79
|
+
in [:receiving_continuation_data, FrameType::WINDOW_UPDATE, :recv]
|
|
80
|
+
state
|
|
79
81
|
in [:receiving_continuation_data, FrameType::CONTINUATION, :recv] if frame.end_headers?
|
|
80
82
|
:receiving_data
|
|
81
83
|
in [:receiving_continuation_data, FrameType::CONTINUATION, :recv]
|
|
@@ -86,6 +88,8 @@ module Biryani
|
|
|
86
88
|
# receiving_continuation
|
|
87
89
|
in [:receiving_continuation, FrameType::RST_STREAM, _]
|
|
88
90
|
:closed
|
|
91
|
+
in [:receiving_continuation, FrameType::WINDOW_UPDATE, :recv]
|
|
92
|
+
state
|
|
89
93
|
in [:receiving_continuation, FrameType::CONTINUATION, :recv] if frame.end_headers?
|
|
90
94
|
:half_closed_remote
|
|
91
95
|
in [:receiving_continuation, FrameType::CONTINUATION, :recv]
|
|
@@ -96,6 +100,8 @@ module Biryani
|
|
|
96
100
|
# receiving_data
|
|
97
101
|
in [:receiving_data, FrameType::DATA, :recv] if frame.end_stream?
|
|
98
102
|
:half_closed_remote
|
|
103
|
+
in [:receiving_data, FrameType::WINDOW_UPDATE, :recv]
|
|
104
|
+
state
|
|
99
105
|
in [:receiving_data, FrameType::DATA, :recv]
|
|
100
106
|
state
|
|
101
107
|
in [:receiving_data, FrameType::RST_STREAM, _]
|
|
@@ -136,6 +142,8 @@ module Biryani
|
|
|
136
142
|
# sending_continuation_data
|
|
137
143
|
in [:sending_continuation_data, FrameType::RST_STREAM, :send]
|
|
138
144
|
:closed
|
|
145
|
+
in [:sending_continuation_data, FrameType::WINDOW_UPDATE, :recv]
|
|
146
|
+
state
|
|
139
147
|
in [:sending_continuation_data, FrameType::CONTINUATION, :send] if frame.end_headers?
|
|
140
148
|
:sending_data
|
|
141
149
|
in [:sending_continuation_data, FrameType::CONTINUATION, :send]
|
|
@@ -148,6 +156,8 @@ module Biryani
|
|
|
148
156
|
# sending_continuation
|
|
149
157
|
in [:sending_continuation, FrameType::RST_STREAM, :send]
|
|
150
158
|
:closed
|
|
159
|
+
in [:sending_continuation, FrameType::WINDOW_UPDATE, :recv]
|
|
160
|
+
state
|
|
151
161
|
in [:sending_continuation, FrameType::CONTINUATION, :send] if frame.end_headers?
|
|
152
162
|
:closed
|
|
153
163
|
in [:sending_continuation, FrameType::CONTINUATION, :send]
|
|
@@ -160,6 +170,8 @@ module Biryani
|
|
|
160
170
|
# sending_data
|
|
161
171
|
in [:sending_data, FrameType::DATA, :send] if frame.end_stream?
|
|
162
172
|
:closed
|
|
173
|
+
in [:sending_data, FrameType::WINDOW_UPDATE, :recv]
|
|
174
|
+
state
|
|
163
175
|
in [:sending_data, FrameType::DATA, :send]
|
|
164
176
|
state
|
|
165
177
|
in [:sending_data, FrameType::RST_STREAM, :send]
|
|
@@ -213,7 +225,7 @@ module Biryani
|
|
|
213
225
|
|
|
214
226
|
# @return [Boolean]
|
|
215
227
|
def active?
|
|
216
|
-
|
|
228
|
+
!%i[idle reserved_local reserved_remote closed].include?(@state)
|
|
217
229
|
end
|
|
218
230
|
|
|
219
231
|
# @return [Boolean]
|