biryani 0.0.1 → 0.0.3
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 +157 -139
- data/lib/biryani/data_buffer.rb +24 -16
- data/lib/biryani/frame/headers.rb +0 -2
- data/lib/biryani/http_response.rb +4 -20
- data/lib/biryani/state.rb +15 -3
- data/lib/biryani/streams_context.rb +54 -8
- data/lib/biryani/utils.rb +23 -0
- data/lib/biryani/version.rb +1 -1
- data/lib/biryani/window.rb +17 -12
- data/lib/biryani.rb +3 -2
- data/spec/connection/handle_connection_window_update_spec.rb +1 -1
- data/spec/connection/handle_data_spec.rb +58 -0
- data/spec/connection/handle_headers_spec.rb +19 -0
- data/spec/connection/handle_rst_stream_spec.rb +4 -9
- data/spec/connection/handle_settings_spec.rb +8 -2
- data/spec/connection/handle_stream_window_update_spec.rb +5 -5
- data/spec/connection/send_spec.rb +38 -48
- data/spec/connection/transition_state_send_spec.rb +4 -4
- data/spec/data_buffer_spec.rb +48 -48
- data/spec/hpack/decoder_spec.rb +33 -33
- data/spec/hpack/encoder_spec.rb +10 -10
- data/spec/hpack/fields_spec.rb +3 -3
- data/spec/streams_context_spec.rb +79 -0
- data/spec/utils_spec.rb +41 -0
- metadata +10 -7
- data/spec/connection/close_all_streams_spec.rb +0 -17
- data/spec/connection/remove_closed_streams_spec.rb +0 -51
- data/spec/connection/unwrap_spec.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 890de8c1fbcd3833f63207a420161b289a4497275fbdbb747625d1350b1f0d6c
|
|
4
|
+
data.tar.gz: 1c587a3643953d2acbd1b0022c1ff1b228cc382cc10c75442fb99d68b370f2b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe5ed688fe4aff77533de4b37ee5e668f7904435a2c345410c4b87c85ab68af33d03b3741fc3266e605554e057c81cfe28e267b1d94e2f916718e2190d6ee948
|
|
7
|
+
data.tar.gz: 19cfdad4968d33626135242e33629ed5105069a30f50e377367fa0aae45e2c594e2cfa388c7ea8b635ab17541749a147c04414738633787a3b720043b38ac6aa
|
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
|
@@ -21,14 +21,14 @@ module Biryani
|
|
|
21
21
|
def initialize(proc)
|
|
22
22
|
@sock = nil # Ractor
|
|
23
23
|
@proc = proc
|
|
24
|
-
@streams_ctx = StreamsContext.new
|
|
24
|
+
@streams_ctx = StreamsContext.new(proc)
|
|
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,36 +76,43 @@ module Biryani
|
|
|
75
76
|
|
|
76
77
|
port, obj = Ractor.select(*ports)
|
|
77
78
|
if port == @sock
|
|
78
|
-
if
|
|
79
|
-
reply_frame =
|
|
79
|
+
if Biryani.err?(obj)
|
|
80
|
+
reply_frame = Biryani.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
|
|
86
87
|
recv_dispatch(obj).each do |frame|
|
|
87
|
-
reply_frame =
|
|
88
|
+
reply_frame = Biryani.unwrap(frame, @streams_ctx.last_stream_id)
|
|
88
89
|
self.class.do_send(io, reply_frame, true)
|
|
90
|
+
if reply_frame.f_type == FrameType::WINDOW_UPDATE && reply_frame.stream_id.zero?
|
|
91
|
+
@recv_window.increase!(reply_frame.window_size_increment)
|
|
92
|
+
elsif reply_frame.f_type == FrameType::WINDOW_UPDATE
|
|
93
|
+
@streams_ctx[reply_frame.stream_id].recv_window.increase!(reply_frame.window_size_increment)
|
|
94
|
+
end
|
|
95
|
+
|
|
89
96
|
close if self.class.transition_state_send(reply_frame, @streams_ctx)
|
|
90
97
|
end
|
|
91
98
|
end
|
|
92
99
|
else
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
res, stream_id = obj
|
|
101
|
+
fragment, data = self.class.http_response(res, @encoder)
|
|
102
|
+
max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
103
|
+
self.class.send_headers(io, stream_id, fragment, data.empty?, max_frame_size, @streams_ctx)
|
|
104
|
+
self.class.send_data(io, stream_id, data, @send_window, max_frame_size, @streams_ctx, @data_buffer) unless data.empty?
|
|
96
105
|
|
|
97
|
-
|
|
106
|
+
@streams_ctx.remove_closed(@data_buffer)
|
|
98
107
|
end
|
|
99
108
|
|
|
100
109
|
break if closed?
|
|
101
110
|
end
|
|
102
|
-
ensure
|
|
103
|
-
self.class.clear_all_streams(@streams_ctx)
|
|
104
111
|
end
|
|
105
112
|
# rubocop: enable Metrics/AbcSize
|
|
106
113
|
# rubocop: enable Metrics/BlockLength
|
|
107
114
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
115
|
+
# rubocop: enable Metrics/MethodLength
|
|
108
116
|
# rubocop: enable Metrics/PerceivedComplexity
|
|
109
117
|
|
|
110
118
|
# @param frame [Object]
|
|
@@ -128,7 +136,7 @@ module Biryani
|
|
|
128
136
|
when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
|
|
129
137
|
[ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
|
|
130
138
|
when FrameType::SETTINGS
|
|
131
|
-
obj = self.class.handle_settings(frame, @
|
|
139
|
+
obj = self.class.handle_settings(frame, @peer_settings, @decoder, @streams_ctx)
|
|
132
140
|
return [] if obj.nil?
|
|
133
141
|
|
|
134
142
|
settings_ack = obj
|
|
@@ -137,18 +145,21 @@ module Biryani
|
|
|
137
145
|
obj = self.class.handle_ping(frame)
|
|
138
146
|
return [] if obj.nil?
|
|
139
147
|
|
|
140
|
-
|
|
148
|
+
ping_ack = obj
|
|
149
|
+
[ping_ack]
|
|
141
150
|
when FrameType::GOAWAY
|
|
142
151
|
self.class.handle_goaway(frame)
|
|
143
|
-
|
|
152
|
+
|
|
144
153
|
[]
|
|
145
154
|
when FrameType::WINDOW_UPDATE
|
|
146
155
|
err = self.class.handle_connection_window_update(frame, @send_window)
|
|
147
156
|
return [err] unless err.nil?
|
|
148
157
|
|
|
149
|
-
|
|
158
|
+
max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
159
|
+
@data_buffer.take!(@send_window, @streams_ctx, max_frame_size) # return DATA Frames
|
|
150
160
|
else
|
|
151
|
-
# ignore
|
|
161
|
+
# ignore UNKNOWN Frame
|
|
162
|
+
|
|
152
163
|
[]
|
|
153
164
|
end
|
|
154
165
|
end
|
|
@@ -160,61 +171,57 @@ module Biryani
|
|
|
160
171
|
# rubocop: disable Metrics/AbcSize
|
|
161
172
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
162
173
|
# rubocop: disable Metrics/MethodLength
|
|
163
|
-
# rubocop: disable Metrics/PerceivedComplexity
|
|
164
174
|
def handle_stream_frame(frame)
|
|
165
175
|
stream_id = frame.stream_id
|
|
166
176
|
typ = frame.f_type
|
|
167
177
|
return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
|
|
168
178
|
if [FrameType::SETTINGS, FrameType::PING, FrameType::GOAWAY].include?(typ)
|
|
169
179
|
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
max_streams = @peer_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS]
|
|
181
|
+
send_initial_window_size = @peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
182
|
+
recv_initial_window_size = @settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
183
|
+
obj = self.class.transition_state_recv(frame, @streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size)
|
|
184
|
+
return [obj] if Biryani.err?(obj)
|
|
172
185
|
|
|
173
186
|
ctx = obj
|
|
174
187
|
case typ
|
|
175
188
|
when FrameType::DATA
|
|
176
|
-
|
|
177
|
-
if
|
|
178
|
-
obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
|
|
179
|
-
return [obj] if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
|
|
189
|
+
obj = self.class.handle_data(stream_id, frame.data, @recv_window, @streams_ctx, @decoder)
|
|
190
|
+
return [obj] if Biryani.err?(obj)
|
|
180
191
|
|
|
181
|
-
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
[]
|
|
192
|
+
obj # return WINDOW_UPDATE Frames
|
|
185
193
|
when FrameType::HEADERS, FrameType::CONTINUATION
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
|
|
189
|
-
return [obj] if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
|
|
190
|
-
|
|
191
|
-
ctx.stream.rx << obj
|
|
192
|
-
end
|
|
194
|
+
err = self.class.handle_headers(frame, ctx, @decoder)
|
|
195
|
+
return [err] unless err.nil?
|
|
193
196
|
|
|
194
197
|
[]
|
|
195
198
|
when FrameType::PRIORITY
|
|
196
199
|
# ignore PRIORITY Frame
|
|
200
|
+
|
|
197
201
|
[]
|
|
198
202
|
when FrameType::PUSH_PROMISE
|
|
199
203
|
# TODO
|
|
204
|
+
|
|
200
205
|
[]
|
|
201
206
|
when FrameType::RST_STREAM
|
|
202
|
-
self.class.handle_rst_stream(frame,
|
|
207
|
+
self.class.handle_rst_stream(frame, ctx)
|
|
208
|
+
|
|
203
209
|
[]
|
|
204
210
|
when FrameType::WINDOW_UPDATE
|
|
205
211
|
err = self.class.handle_stream_window_update(frame, @streams_ctx)
|
|
206
212
|
return [err] unless err.nil?
|
|
207
213
|
|
|
208
|
-
|
|
214
|
+
max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
215
|
+
@data_buffer.take!(@send_window, @streams_ctx, max_frame_size)
|
|
209
216
|
else
|
|
210
217
|
# ignore UNKNOWN Frame
|
|
218
|
+
|
|
211
219
|
[]
|
|
212
220
|
end
|
|
213
221
|
end
|
|
214
222
|
# rubocop: enable Metrics/AbcSize
|
|
215
223
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
216
224
|
# rubocop: enable Metrics/MethodLength
|
|
217
|
-
# rubocop: enable Metrics/PerceivedComplexity
|
|
218
225
|
|
|
219
226
|
def close
|
|
220
227
|
@closed = true
|
|
@@ -225,39 +232,25 @@ module Biryani
|
|
|
225
232
|
@closed
|
|
226
233
|
end
|
|
227
234
|
|
|
228
|
-
# @param obj [Object, ConnectionError, StreamError] frame or error
|
|
229
|
-
# @param last_stream_id [Integer]
|
|
230
|
-
#
|
|
231
|
-
# @return [Frame]
|
|
232
|
-
def self.unwrap(obj, last_stream_id)
|
|
233
|
-
case obj
|
|
234
|
-
when ConnectionError
|
|
235
|
-
obj.goaway(last_stream_id)
|
|
236
|
-
when StreamError
|
|
237
|
-
obj.rst_stream
|
|
238
|
-
else
|
|
239
|
-
obj
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
235
|
# @param recv_frame [Object]
|
|
244
236
|
# @param streams_ctx [StreamsContext]
|
|
245
237
|
# @param stream_id [Integer]
|
|
246
238
|
# @param max_streams [Integer]
|
|
247
|
-
# @param
|
|
239
|
+
# @param send_initial_window_size [Integer]
|
|
240
|
+
# @param recv_initial_window_size [Integer]
|
|
248
241
|
#
|
|
249
242
|
# @return [StreamContext, StreamError, ConnectionError]
|
|
250
243
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
251
244
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
252
|
-
def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams,
|
|
245
|
+
def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size)
|
|
253
246
|
ctx = streams_ctx[stream_id]
|
|
254
247
|
return StreamError.new(ErrorCode::PROTOCOL_ERROR, stream_id, 'exceed max concurrent streams') if ctx.nil? && streams_ctx.count_active + 1 > max_streams
|
|
255
248
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'even-numbered stream identifier') if ctx.nil? && stream_id.even?
|
|
256
249
|
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
250
|
|
|
258
|
-
ctx = streams_ctx.new_context(stream_id,
|
|
251
|
+
ctx = streams_ctx.new_context(stream_id, send_initial_window_size, recv_initial_window_size) if ctx.nil?
|
|
259
252
|
obj = ctx.state.transition!(recv_frame, :recv)
|
|
260
|
-
return obj if
|
|
253
|
+
return obj if Biryani.err?(obj)
|
|
261
254
|
|
|
262
255
|
ctx
|
|
263
256
|
end
|
|
@@ -275,46 +268,14 @@ module Biryani
|
|
|
275
268
|
when FrameType::SETTINGS, FrameType::PING
|
|
276
269
|
false
|
|
277
270
|
when FrameType::GOAWAY
|
|
278
|
-
|
|
271
|
+
streams_ctx.close_all
|
|
279
272
|
true
|
|
280
273
|
else
|
|
281
|
-
streams_ctx[stream_id].state.transition!(send_frame, :send)
|
|
274
|
+
streams_ctx[stream_id].state.transition!(send_frame, :send) unless stream_id.zero?
|
|
282
275
|
false
|
|
283
276
|
end
|
|
284
277
|
end
|
|
285
278
|
|
|
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
279
|
# @param io [IO]
|
|
319
280
|
# @param frame [Object]
|
|
320
281
|
# @param flush [Boolean]
|
|
@@ -324,39 +285,46 @@ module Biryani
|
|
|
324
285
|
end
|
|
325
286
|
|
|
326
287
|
# @param io [IO]
|
|
327
|
-
# @param
|
|
288
|
+
# @param stream_id [Integer]
|
|
289
|
+
# @param data [String]
|
|
328
290
|
# @param send_window [Window]
|
|
291
|
+
# @param max_frame_size [Integer]
|
|
329
292
|
# @param streams_ctx [StreamsContext]
|
|
330
293
|
# @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
|
|
294
|
+
def self.send_data(io, stream_id, data, send_window, max_frame_size, streams_ctx, data_buffer)
|
|
295
|
+
frames, remains = streams_ctx.sendable_datas(stream_id, data, send_window, max_frame_size)
|
|
338
296
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
send_window.consume!(
|
|
343
|
-
streams_ctx
|
|
344
|
-
return transition_state_send(frame, streams_ctx)
|
|
297
|
+
frames.each do |frame|
|
|
298
|
+
do_send(io, frame, false)
|
|
299
|
+
send_window.consume!(frame.length)
|
|
300
|
+
streams_ctx[stream_id].send_window.consume!(frame.length)
|
|
301
|
+
transition_state_send(frame, streams_ctx)
|
|
345
302
|
end
|
|
346
303
|
|
|
347
|
-
data_buffer
|
|
348
|
-
false
|
|
304
|
+
data_buffer.store(stream_id, remains) unless remains.empty?
|
|
349
305
|
end
|
|
350
306
|
|
|
351
|
-
# @param
|
|
352
|
-
# @param
|
|
307
|
+
# @param io [IO]
|
|
308
|
+
# @param stream_id [Integer]
|
|
309
|
+
# @param fragment [String]
|
|
310
|
+
# @param only_headers [Boolean]
|
|
311
|
+
# @param max_frame_size [Integer]
|
|
353
312
|
# @param streams_ctx [StreamsContext]
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
313
|
+
def self.send_headers(io, stream_id, fragment, only_headers, max_frame_size, streams_ctx)
|
|
314
|
+
len = (fragment.bytesize + max_frame_size - 1) / max_frame_size
|
|
315
|
+
frames = fragment.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
|
|
316
|
+
end_headers = index == len - 1
|
|
317
|
+
if index.zero?
|
|
318
|
+
Frame::Headers.new(end_headers, only_headers, stream_id, nil, nil, s, nil)
|
|
319
|
+
else
|
|
320
|
+
Frame::Continuation.new(end_headers, stream_id, s)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
frames.each do |frame|
|
|
325
|
+
do_send(io, frame, false)
|
|
326
|
+
transition_state_send(frame, streams_ctx)
|
|
327
|
+
end
|
|
360
328
|
end
|
|
361
329
|
|
|
362
330
|
# @param io [IO]
|
|
@@ -367,29 +335,80 @@ module Biryani
|
|
|
367
335
|
ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid connection preface') if s != CONNECTION_PREFACE
|
|
368
336
|
end
|
|
369
337
|
|
|
370
|
-
# @param
|
|
338
|
+
# @param stream_id [Integer]
|
|
339
|
+
# @param data [String]
|
|
340
|
+
# @param recv_window [Window]
|
|
371
341
|
# @param streams_ctx [StreamsContext]
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
342
|
+
# @param decoder [Decoder]
|
|
343
|
+
#
|
|
344
|
+
# @return [Array<WindowUpdate>, ConnectionError]
|
|
345
|
+
# rubocop: disable Metrics/AbcSize
|
|
346
|
+
def self.handle_data(stream_id, data, recv_window, streams_ctx, decoder)
|
|
347
|
+
ctx = streams_ctx[stream_id]
|
|
348
|
+
return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'DATA Frame length exceeds flow-control window size') \
|
|
349
|
+
if recv_window.consume!(data.bytesize).negative? || ctx.recv_window.consume!(data.bytesize).negative?
|
|
350
|
+
|
|
351
|
+
ctx.content << data
|
|
352
|
+
if ctx.state.half_closed_remote?
|
|
353
|
+
obj = http_request(ctx.fragment.string, ctx.content.string, decoder)
|
|
354
|
+
return obj if Biryani.err?(obj)
|
|
355
|
+
|
|
356
|
+
ctx.stream.rx << obj
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
window_updates = []
|
|
360
|
+
window_updates << Frame::WindowUpdate.new(0, recv_window.capacity - recv_window.length) if recv_window.length < recv_window.capacity / 2
|
|
361
|
+
window_updates << Frame::WindowUpdate.new(stream_id, ctx.recv_window.capacity - ctx.recv_window.length) if ctx.recv_window.length < ctx.recv_window.capacity / 2
|
|
362
|
+
window_updates
|
|
363
|
+
end
|
|
364
|
+
# rubocop: enable Metrics/AbcSize
|
|
365
|
+
|
|
366
|
+
# @param headers [Headers]
|
|
367
|
+
# @param ctx [StreamContext]
|
|
368
|
+
# @param decoder [Decoder]
|
|
369
|
+
#
|
|
370
|
+
# @return [nil, ConnectionError]
|
|
371
|
+
def self.handle_headers(headers, ctx, decoder)
|
|
372
|
+
ctx.fragment << headers.fragment
|
|
373
|
+
if ctx.state.half_closed_remote?
|
|
374
|
+
obj = http_request(ctx.fragment.string, ctx.content.string, decoder)
|
|
375
|
+
return [obj] if Biryani.err?(obj)
|
|
376
|
+
|
|
377
|
+
ctx.stream.rx << obj
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
nil
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# @param _rst_stream [RstStream]
|
|
384
|
+
# @param ctx [StreamContext]
|
|
385
|
+
def self.handle_rst_stream(_rst_stream, ctx)
|
|
386
|
+
ctx.state.close
|
|
375
387
|
end
|
|
376
388
|
|
|
377
389
|
# @param settings [Settings]
|
|
378
|
-
# @param
|
|
390
|
+
# @param peer_settings [Hash<Integer, Integer>]
|
|
379
391
|
# @param decoder [Decoder]
|
|
392
|
+
# @param streams_ctx [StreamsContext]
|
|
380
393
|
#
|
|
381
394
|
# @return [Settings]
|
|
382
|
-
def self.handle_settings(settings,
|
|
395
|
+
def self.handle_settings(settings, peer_settings, decoder, streams_ctx)
|
|
383
396
|
return nil if settings.ack?
|
|
384
397
|
|
|
385
|
-
|
|
386
|
-
|
|
398
|
+
peer_settings.merge!(settings.setting)
|
|
399
|
+
new_limit = peer_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE]
|
|
400
|
+
decoder.limit!(new_limit)
|
|
401
|
+
send_initial_window_size = peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
402
|
+
streams_ctx.each do |ctx|
|
|
403
|
+
ctx.send_window.update!(send_initial_window_size)
|
|
404
|
+
end
|
|
405
|
+
|
|
387
406
|
Frame::Settings.new(true, 0, {})
|
|
388
407
|
end
|
|
389
408
|
|
|
390
409
|
# @param ping [Ping]
|
|
391
410
|
#
|
|
392
|
-
# @return [Ping, nil
|
|
411
|
+
# @return [Ping, nil]
|
|
393
412
|
def self.handle_ping(ping)
|
|
394
413
|
Frame::Ping.new(true, 0, ping.opaque) unless ping.ack?
|
|
395
414
|
end
|
|
@@ -402,21 +421,21 @@ module Biryani
|
|
|
402
421
|
#
|
|
403
422
|
# @return [nil, ConnectionError]
|
|
404
423
|
def self.handle_connection_window_update(window_update, send_window)
|
|
405
|
-
# TODO: send WINDOW_UPDATE
|
|
406
424
|
send_window.increase!(window_update.window_size_increment)
|
|
425
|
+
return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'flow-control window exceeds 2^31-1') if send_window.length > 2**31 - 1
|
|
426
|
+
|
|
407
427
|
nil
|
|
408
428
|
end
|
|
409
429
|
|
|
410
430
|
# @param window_update [WindowUpdate]
|
|
411
431
|
# @param streams_ctx [StreamsContext]
|
|
412
432
|
#
|
|
413
|
-
# @return [nil,
|
|
433
|
+
# @return [nil, StreamError]
|
|
414
434
|
def self.handle_stream_window_update(window_update, streams_ctx)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
435
|
+
stream_id = window_update.stream_id
|
|
436
|
+
streams_ctx[stream_id].send_window.increase!(window_update.window_size_increment)
|
|
437
|
+
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
438
|
|
|
419
|
-
streams_ctx[window_update.stream_id].send_window.increase!(window_update.window_size_increment)
|
|
420
439
|
nil
|
|
421
440
|
end
|
|
422
441
|
|
|
@@ -427,7 +446,7 @@ module Biryani
|
|
|
427
446
|
# @return [HTTPRequest, ConnectionError]
|
|
428
447
|
def self.http_request(fragment, content, decoder)
|
|
429
448
|
obj = decoder.decode(fragment)
|
|
430
|
-
return obj if
|
|
449
|
+
return obj if Biryani.err?(obj)
|
|
431
450
|
|
|
432
451
|
fields = obj
|
|
433
452
|
builder = HTTPRequestBuilder.new
|
|
@@ -438,13 +457,12 @@ module Biryani
|
|
|
438
457
|
end
|
|
439
458
|
|
|
440
459
|
# @param res [HTTPResponse]
|
|
441
|
-
# @param stream_id [Integer]
|
|
442
460
|
# @param encoder [Encoder]
|
|
443
|
-
# @param max_frame_size [Integer]
|
|
444
461
|
#
|
|
445
|
-
# @return [
|
|
446
|
-
|
|
447
|
-
|
|
462
|
+
# @return [String] fragment
|
|
463
|
+
# @return [String] data
|
|
464
|
+
def self.http_response(res, encoder)
|
|
465
|
+
HTTPResponseParser.new(res).parse(encoder)
|
|
448
466
|
end
|
|
449
467
|
|
|
450
468
|
# @return [Hash<Integer, Integer>]
|
data/lib/biryani/data_buffer.rb
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
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_datas(stream_id, data, send_window, max_frame_size)
|
|
23
|
+
datas += frames
|
|
24
|
+
if remains.empty?
|
|
25
|
+
@buffer.delete(stream_id)
|
|
26
|
+
else
|
|
27
|
+
@buffer[stream_id] = remains
|
|
28
|
+
end
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
len = frames.map(&:length).sum
|
|
31
|
+
send_window.consume!(len)
|
|
32
|
+
streams_ctx[stream_id].send_window.consume!(len)
|
|
24
33
|
end
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
datas.values
|
|
35
|
+
datas
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
# @return [Integer]
|
|
@@ -36,7 +44,7 @@ module Biryani
|
|
|
36
44
|
#
|
|
37
45
|
# @return [Boolean]
|
|
38
46
|
def has?(stream_id)
|
|
39
|
-
@buffer.
|
|
47
|
+
@buffer.key?(stream_id)
|
|
40
48
|
end
|
|
41
49
|
end
|
|
42
50
|
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?
|