http-protocol 0.1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/Gemfile +11 -0
- data/README.md +64 -0
- data/Rakefile +6 -0
- data/http-protocol.gemspec +24 -0
- data/lib/http/protocol.rb +21 -0
- data/lib/http/protocol/error.rb +80 -0
- data/lib/http/protocol/headers.rb +203 -0
- data/lib/http/protocol/http2/client.rb +49 -0
- data/lib/http/protocol/http2/connection.rb +309 -0
- data/lib/http/protocol/http2/continuation_frame.rb +86 -0
- data/lib/http/protocol/http2/data_frame.rb +64 -0
- data/lib/http/protocol/http2/flow_control.rb +85 -0
- data/lib/http/protocol/http2/frame.rb +187 -0
- data/lib/http/protocol/http2/framer.rb +105 -0
- data/lib/http/protocol/http2/goaway_frame.rb +62 -0
- data/lib/http/protocol/http2/headers_frame.rb +95 -0
- data/lib/http/protocol/http2/padded.rb +91 -0
- data/lib/http/protocol/http2/ping_frame.rb +82 -0
- data/lib/http/protocol/http2/priority_frame.rb +82 -0
- data/lib/http/protocol/http2/push_promise_frame.rb +67 -0
- data/lib/http/protocol/http2/reset_stream_frame.rb +74 -0
- data/lib/http/protocol/http2/server.rb +47 -0
- data/lib/http/protocol/http2/settings_frame.rb +242 -0
- data/lib/http/protocol/http2/stream.rb +313 -0
- data/lib/http/protocol/http2/window_update_frame.rb +89 -0
- data/lib/http/protocol/version.rb +25 -0
- metadata +128 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'connection'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
class Client < Connection
|
27
|
+
def initialize(framer, *args)
|
28
|
+
super(framer, 1, *args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_connection_preface(settings = nil)
|
32
|
+
if @state == :new
|
33
|
+
@framer.write_connection_preface
|
34
|
+
|
35
|
+
send_settings(settings)
|
36
|
+
|
37
|
+
yield if block_given?
|
38
|
+
|
39
|
+
read_frame do |frame|
|
40
|
+
raise ProtocolError, "First frame must be SettingsFrame, but got #{frame.class}" unless frame.is_a? SettingsFrame
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise ProtocolError, "Cannot send connection preface in state #{@state}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'framer'
|
22
|
+
require_relative 'flow_control'
|
23
|
+
|
24
|
+
require 'http/hpack/context'
|
25
|
+
require 'http/hpack/compressor'
|
26
|
+
require 'http/hpack/decompressor'
|
27
|
+
|
28
|
+
module HTTP
|
29
|
+
module Protocol
|
30
|
+
module HTTP2
|
31
|
+
class Connection
|
32
|
+
include FlowControl
|
33
|
+
|
34
|
+
def initialize(framer, next_stream_id)
|
35
|
+
@state = :new
|
36
|
+
@streams = {}
|
37
|
+
|
38
|
+
@framer = framer
|
39
|
+
@next_stream_id = next_stream_id
|
40
|
+
@last_stream_id = 0
|
41
|
+
|
42
|
+
@local_settings = PendingSettings.new
|
43
|
+
@remote_settings = Settings.new
|
44
|
+
|
45
|
+
@decoder = HPACK::Context.new
|
46
|
+
@encoder = HPACK::Context.new
|
47
|
+
|
48
|
+
@local_window = Window.new(@local_settings.initial_window_size)
|
49
|
+
@remote_window = Window.new(@remote_settings.initial_window_size)
|
50
|
+
end
|
51
|
+
|
52
|
+
def id
|
53
|
+
0
|
54
|
+
end
|
55
|
+
|
56
|
+
def maximum_frame_size
|
57
|
+
@remote_settings.maximum_frame_size
|
58
|
+
end
|
59
|
+
|
60
|
+
def maximum_concurrent_streams
|
61
|
+
[@local_settings.maximum_concurrent_streams, @remote_settings.maximum_concurrent_streams].min
|
62
|
+
end
|
63
|
+
|
64
|
+
attr :framer
|
65
|
+
|
66
|
+
# Connection state (:new, :open, :closed).
|
67
|
+
attr_accessor :state
|
68
|
+
|
69
|
+
# Current settings value for local and peer
|
70
|
+
attr_accessor :local_settings
|
71
|
+
attr_accessor :remote_settings
|
72
|
+
|
73
|
+
# Our window for receiving data. When we receive data, it reduces this window.
|
74
|
+
# If the window gets too small, we must send a window update.
|
75
|
+
attr :local_window
|
76
|
+
|
77
|
+
# Our window for sending data. When we send data, it reduces this window.
|
78
|
+
attr :remote_window
|
79
|
+
|
80
|
+
def closed?
|
81
|
+
@state == :closed
|
82
|
+
end
|
83
|
+
|
84
|
+
def encode_headers(headers, buffer = String.new.b)
|
85
|
+
HPACK::Compressor.new(buffer, @encoder).encode(headers)
|
86
|
+
|
87
|
+
return buffer
|
88
|
+
end
|
89
|
+
|
90
|
+
def decode_headers(data)
|
91
|
+
HPACK::Decompressor.new(data, @decoder).decode
|
92
|
+
end
|
93
|
+
|
94
|
+
# 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
|
+
def next_stream_id
|
96
|
+
id = @next_stream_id
|
97
|
+
|
98
|
+
@next_stream_id += 2
|
99
|
+
|
100
|
+
return id
|
101
|
+
end
|
102
|
+
|
103
|
+
attr :streams
|
104
|
+
|
105
|
+
def read_frame
|
106
|
+
frame = @framer.read_frame
|
107
|
+
# puts "#{self.class} #{@state} read_frame: #{frame.inspect}"
|
108
|
+
|
109
|
+
yield frame if block_given?
|
110
|
+
|
111
|
+
frame.apply(self)
|
112
|
+
|
113
|
+
return frame
|
114
|
+
rescue ProtocolError => error
|
115
|
+
send_goaway(error.code || PROTOCOL_ERROR, error.message)
|
116
|
+
raise
|
117
|
+
end
|
118
|
+
|
119
|
+
def send_settings(changes)
|
120
|
+
@local_settings.append(changes)
|
121
|
+
|
122
|
+
frame = SettingsFrame.new
|
123
|
+
frame.pack(changes)
|
124
|
+
|
125
|
+
write_frame(frame)
|
126
|
+
end
|
127
|
+
|
128
|
+
def send_goaway(error_code = 0, message = "")
|
129
|
+
frame = GoawayFrame.new
|
130
|
+
frame.pack @last_stream_id, error_code, message
|
131
|
+
|
132
|
+
write_frame(frame)
|
133
|
+
|
134
|
+
@state = :closed
|
135
|
+
end
|
136
|
+
|
137
|
+
def write_frame(frame)
|
138
|
+
# puts "#{self.class} #{@state} write_frame: #{frame.inspect}"
|
139
|
+
@framer.write_frame(frame)
|
140
|
+
end
|
141
|
+
|
142
|
+
def send_ping(data)
|
143
|
+
if @state != :closed
|
144
|
+
frame = PingFrame.new
|
145
|
+
frame.pack data
|
146
|
+
|
147
|
+
write_frame(frame)
|
148
|
+
else
|
149
|
+
raise ProtocolError, "Cannot send ping in state #{@state}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def update_local_settings(changes)
|
154
|
+
capacity = @local_settings.initial_window_size
|
155
|
+
@streams.each_value do |stream|
|
156
|
+
stream.local_window.capacity = capacity
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def update_remote_settings(changes)
|
161
|
+
capacity = @remote_settings.initial_window_size
|
162
|
+
@streams.each_value do |stream|
|
163
|
+
stream.remote_window.capacity = capacity
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# 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.
|
168
|
+
#
|
169
|
+
# @return [Boolean] whether the frame was an acknowledgement
|
170
|
+
def process_settings(frame)
|
171
|
+
if frame.acknowledgement?
|
172
|
+
# The remote end has confirmed the settings have been received:
|
173
|
+
changes = @local_settings.acknowledge
|
174
|
+
|
175
|
+
update_local_settings(changes)
|
176
|
+
|
177
|
+
return true
|
178
|
+
else
|
179
|
+
# The remote end is updating the settings, we reply with acknowledgement:
|
180
|
+
reply = frame.acknowledge
|
181
|
+
|
182
|
+
write_frame(reply)
|
183
|
+
|
184
|
+
changes = frame.unpack
|
185
|
+
@remote_settings.update(changes)
|
186
|
+
|
187
|
+
update_remote_settings(changes)
|
188
|
+
|
189
|
+
return false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def open!
|
194
|
+
@local_window.capacity = self.local_settings.initial_window_size
|
195
|
+
@remote_window.capacity = self.remote_settings.initial_window_size
|
196
|
+
|
197
|
+
@state = :open
|
198
|
+
|
199
|
+
return self
|
200
|
+
end
|
201
|
+
|
202
|
+
def receive_settings(frame)
|
203
|
+
if @state == :new
|
204
|
+
# We transition to :open when we receive acknowledgement of first settings frame:
|
205
|
+
open! if process_settings(frame)
|
206
|
+
elsif @state != :closed
|
207
|
+
process_settings(frame)
|
208
|
+
else
|
209
|
+
raise ProtocolError, "Cannot receive settings in state #{@state}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def receive_ping(frame)
|
214
|
+
if @state != :closed
|
215
|
+
unless frame.acknowledgement?
|
216
|
+
reply = frame.acknowledge
|
217
|
+
|
218
|
+
write_frame(reply)
|
219
|
+
end
|
220
|
+
else
|
221
|
+
raise ProtocolError, "Cannot receive ping in state #{@state}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def receive_data(frame)
|
226
|
+
consume_local_window(frame)
|
227
|
+
|
228
|
+
if stream = @streams[frame.stream_id]
|
229
|
+
stream.receive_data(frame)
|
230
|
+
|
231
|
+
if stream.closed?
|
232
|
+
@streams.delete(stream.id)
|
233
|
+
end
|
234
|
+
else
|
235
|
+
raise ProtocolError, "Bad stream"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def create_stream(stream_id = next_stream_id)
|
240
|
+
stream = Stream.new(self, stream_id)
|
241
|
+
|
242
|
+
@last_stream_id = stream_id
|
243
|
+
|
244
|
+
return stream
|
245
|
+
end
|
246
|
+
|
247
|
+
def receive_headers(frame)
|
248
|
+
if stream = @streams[frame.stream_id]
|
249
|
+
stream.receive_headers(frame)
|
250
|
+
|
251
|
+
if stream.closed?
|
252
|
+
@streams.delete(stream.id)
|
253
|
+
end
|
254
|
+
elsif frame.stream_id > @last_stream_id
|
255
|
+
if @streams.count < self.maximum_concurrent_streams
|
256
|
+
stream = create_stream(frame.stream_id)
|
257
|
+
stream.receive_headers(frame)
|
258
|
+
|
259
|
+
@streams[stream.id] = stream
|
260
|
+
else
|
261
|
+
raise ProtocolError, "Exceeded maximum concurrent streams"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def receive_priority(frame)
|
267
|
+
if stream = @streams[frame.stream_id]
|
268
|
+
stream.receive_priority(frame)
|
269
|
+
else
|
270
|
+
raise ProtocolError, "Bad stream"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def receive_reset_stream(frame)
|
275
|
+
if stream = @streams[frame.stream_id]
|
276
|
+
stream.receive_reset_stream(frame)
|
277
|
+
|
278
|
+
@streams.delete(stream.id)
|
279
|
+
else
|
280
|
+
raise ProtocolError, "Bad stream"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def receive_window_update(frame)
|
285
|
+
if frame.connection?
|
286
|
+
super
|
287
|
+
elsif stream = @streams[frame.stream_id]
|
288
|
+
stream.receive_window_update(frame)
|
289
|
+
elsif frame.stream_id <= @last_stream_id
|
290
|
+
# The stream was closed/deleted, ignore
|
291
|
+
else
|
292
|
+
raise ProtocolError, "Cannot update window of non-existant stream: #{frame.stream_id}"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def window_updated
|
297
|
+
# This is very inefficient, but workable.
|
298
|
+
@streams.each_value do |stream|
|
299
|
+
stream.window_updated unless stream.closed?
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def receive_frame(frame)
|
304
|
+
warn "Unhandled frame #{frame.inspect}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'frame'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
module Continued
|
27
|
+
def end_headers?
|
28
|
+
@flags & END_HEADERS
|
29
|
+
end
|
30
|
+
|
31
|
+
def read(io)
|
32
|
+
super
|
33
|
+
|
34
|
+
unless end_headers?
|
35
|
+
@continuation = ContinuationFrame.new
|
36
|
+
|
37
|
+
@continuation.read(io)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def write(io)
|
42
|
+
super
|
43
|
+
|
44
|
+
if continuation = self.continuation
|
45
|
+
continuation.write(io)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :continuation
|
50
|
+
|
51
|
+
def pack(data, **options)
|
52
|
+
maximum_size = options[:maximum_size]
|
53
|
+
|
54
|
+
if maximum_size and data.bytesize > maximum_size
|
55
|
+
clear_flags(END_HEADERS)
|
56
|
+
|
57
|
+
super(data.byteslice(0, maximum_size), **options)
|
58
|
+
|
59
|
+
remainder = data.byteslice(maximum_size, data.bytesize-maximum_size)
|
60
|
+
|
61
|
+
@continuation = ContinuationFrame.new
|
62
|
+
@continuation.pack(remainder, maximum_size: maximum_size)
|
63
|
+
else
|
64
|
+
set_flags(END_HEADERS)
|
65
|
+
|
66
|
+
super data, **options
|
67
|
+
|
68
|
+
@continuation = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# The CONTINUATION frame is used to continue a sequence of header block fragments. Any number of CONTINUATION frames can be sent, as long as the preceding frame is on the same stream and is a HEADERS, PUSH_PROMISE, or CONTINUATION frame without the END_HEADERS flag set.
|
74
|
+
#
|
75
|
+
# +---------------------------------------------------------------+
|
76
|
+
# | Header Block Fragment (*) ...
|
77
|
+
# +---------------------------------------------------------------+
|
78
|
+
#
|
79
|
+
class ContinuationFrame < Frame
|
80
|
+
include Continued
|
81
|
+
|
82
|
+
TYPE = 0x9
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
# Copyright, 2013, by Ilya Grigorik.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
require_relative 'frame'
|
23
|
+
require_relative 'padded'
|
24
|
+
|
25
|
+
module HTTP
|
26
|
+
module Protocol
|
27
|
+
module HTTP2
|
28
|
+
# DATA frames convey arbitrary, variable-length sequences of octets associated with a stream. One or more DATA frames are used, for instance, to carry HTTP request or response payloads.
|
29
|
+
#
|
30
|
+
# DATA frames MAY also contain padding. Padding can be added to DATA frames to obscure the size of messages.
|
31
|
+
#
|
32
|
+
# +---------------+
|
33
|
+
# |Pad Length? (8)|
|
34
|
+
# +---------------+-----------------------------------------------+
|
35
|
+
# | Data (*) ...
|
36
|
+
# +---------------------------------------------------------------+
|
37
|
+
# | Padding (*) ...
|
38
|
+
# +---------------------------------------------------------------+
|
39
|
+
#
|
40
|
+
class DataFrame < Frame
|
41
|
+
include Padded
|
42
|
+
|
43
|
+
TYPE = 0x0
|
44
|
+
|
45
|
+
def end_stream?
|
46
|
+
flag_set?(END_STREAM)
|
47
|
+
end
|
48
|
+
|
49
|
+
def pack(data, *)
|
50
|
+
if data
|
51
|
+
super
|
52
|
+
else
|
53
|
+
@length = 0
|
54
|
+
set_flags(END_STREAM)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def apply(connection)
|
59
|
+
connection.receive_data(self)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|