http-protocol 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,242 @@
|
|
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 'ping_frame'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
class Settings
|
27
|
+
MAXIMUM_ALLOWED_WINDOW_SIZE = 0x7FFFFFFF
|
28
|
+
MAXIMUM_ALLOWED_FRAME_SIZE = 0xFFFFFF
|
29
|
+
|
30
|
+
HEADER_TABLE_SIZE = 0x1
|
31
|
+
ENABLE_PUSH = 0x2
|
32
|
+
MAXIMUM_CONCURRENT_STREAMS = 0x3
|
33
|
+
INITIAL_WINDOW_SIZE = 0x4
|
34
|
+
MAXIMUM_FRAME_SIZE = 0x5
|
35
|
+
MAXIMUM_HEADER_LIST_SIZE = 0x6
|
36
|
+
|
37
|
+
# Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets.
|
38
|
+
attr_accessor :header_table_size
|
39
|
+
|
40
|
+
# This setting can be used to disable server push. An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
|
41
|
+
attr :enable_push
|
42
|
+
|
43
|
+
def enable_push= value
|
44
|
+
if @enable_push == 0 || @enable_push == 1
|
45
|
+
@enable_push = value
|
46
|
+
else
|
47
|
+
raise ProtocolError, "Invalid value for enable_push: #{value}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def enable_push?
|
52
|
+
@enable_push != 0
|
53
|
+
end
|
54
|
+
|
55
|
+
# Indicates the maximum number of concurrent streams that the sender will allow.
|
56
|
+
attr_accessor :maximum_concurrent_streams
|
57
|
+
|
58
|
+
# Indicates the sender's initial window size (in octets) for stream-level flow control.
|
59
|
+
attr :initial_window_size
|
60
|
+
|
61
|
+
def initial_window_size= value
|
62
|
+
if value < MAXIMUM_ALLOWED_WINDOW_SIZE
|
63
|
+
@initial_window_size = value
|
64
|
+
else
|
65
|
+
raise FlowControlError, "Invalid value for initial_window_size: #{value} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
|
70
|
+
attr :maximum_frame_size
|
71
|
+
|
72
|
+
def maximum_frame_size= value
|
73
|
+
if value <= MAXIMUM_ALLOWED_FRAME_SIZE
|
74
|
+
@maximum_frame_size = value
|
75
|
+
else
|
76
|
+
raise ProtocolError, "Invalid value for maximum_frame_size: #{value} > #{MAXIMUM_ALLOWED_FRAME_SIZE}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
|
81
|
+
attr_accessor :maximum_header_list_size
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
# These limits are taken from the RFC:
|
85
|
+
# https://tools.ietf.org/html/rfc7540#section-6.5.2
|
86
|
+
@header_table_size = 4096
|
87
|
+
@enable_push = 1
|
88
|
+
@maximum_concurrent_streams = 0xFFFFFFFF
|
89
|
+
@initial_window_size = 0xFFFF # 2**16 - 1
|
90
|
+
@maximum_frame_size = 0x3FFF # 2**14 - 1
|
91
|
+
@maximum_header_list_size = 0xFFFFFFFF
|
92
|
+
end
|
93
|
+
|
94
|
+
ASSIGN = [
|
95
|
+
nil,
|
96
|
+
:header_table_size=,
|
97
|
+
:enable_push=,
|
98
|
+
:maximum_concurrent_streams=,
|
99
|
+
:initial_window_size=,
|
100
|
+
:maximum_frame_size=,
|
101
|
+
:maximum_header_list_size=,
|
102
|
+
]
|
103
|
+
|
104
|
+
def update(changes)
|
105
|
+
changes.each do |key, value|
|
106
|
+
if name = ASSIGN[key]
|
107
|
+
self.send(name, value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def difference(other)
|
113
|
+
changes = []
|
114
|
+
|
115
|
+
if @header_table_size != other.header_table_size
|
116
|
+
changes << [HEADER_TABLE_SIZE, @header_table_size]
|
117
|
+
end
|
118
|
+
|
119
|
+
if @enable_push != other.enable_push
|
120
|
+
changes << [ENABLE_PUSH, @enable_push ? 1 : 0]
|
121
|
+
end
|
122
|
+
|
123
|
+
if @maximum_concurrent_streams != other.maximum_concurrent_streams
|
124
|
+
changes << [MAXIMUM_CONCURRENT_STREAMS, @maximum_concurrent_streams]
|
125
|
+
end
|
126
|
+
|
127
|
+
if @initial_window_size != other.initial_window_size
|
128
|
+
changes << [INITIAL_WINDOW_SIZE, @initial_window_size]
|
129
|
+
end
|
130
|
+
|
131
|
+
if @maximum_frame_size != other.maximum_frame_size
|
132
|
+
changes << [MAXIMUM_FRAME_SIZE, @maximum_frame_size]
|
133
|
+
end
|
134
|
+
|
135
|
+
if @maximum_header_list_size != other.maximum_header_list_size
|
136
|
+
changes << [MAXIMUM_HEADER_LIST_SIZE, @maximum_header_list_size]
|
137
|
+
end
|
138
|
+
|
139
|
+
return changes
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class PendingSettings
|
144
|
+
def initialize(current = Settings.new)
|
145
|
+
@current = current
|
146
|
+
@pending = current.dup
|
147
|
+
|
148
|
+
@queue = []
|
149
|
+
end
|
150
|
+
|
151
|
+
attr :current
|
152
|
+
attr :pending
|
153
|
+
|
154
|
+
def append(changes)
|
155
|
+
@queue << changes
|
156
|
+
@pending.update(changes)
|
157
|
+
end
|
158
|
+
|
159
|
+
def acknowledge
|
160
|
+
if changes = @queue.shift
|
161
|
+
@current.update(changes)
|
162
|
+
|
163
|
+
return changes
|
164
|
+
else
|
165
|
+
raise ProtocolError.new("Cannot acknowledge settings, no changes pending")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def header_table_size
|
170
|
+
@current.header_table_size
|
171
|
+
end
|
172
|
+
|
173
|
+
def enable_push
|
174
|
+
@current.enable_push
|
175
|
+
end
|
176
|
+
|
177
|
+
def maximum_concurrent_streams
|
178
|
+
@current.maximum_concurrent_streams
|
179
|
+
end
|
180
|
+
|
181
|
+
def initial_window_size
|
182
|
+
@current.initial_window_size
|
183
|
+
end
|
184
|
+
|
185
|
+
def maximum_frame_size
|
186
|
+
@current.maximum_frame_size
|
187
|
+
end
|
188
|
+
|
189
|
+
def maximum_header_list_size
|
190
|
+
@current.maximum_header_list_size
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# The SETTINGS frame conveys configuration parameters that affect how endpoints communicate, such as preferences and constraints on peer behavior. The SETTINGS frame is also used to acknowledge the receipt of those parameters. Individually, a SETTINGS parameter can also be referred to as a "setting".
|
195
|
+
#
|
196
|
+
# +-------------------------------+
|
197
|
+
# | Identifier (16) |
|
198
|
+
# +-------------------------------+-------------------------------+
|
199
|
+
# | Value (32) |
|
200
|
+
# +---------------------------------------------------------------+
|
201
|
+
#
|
202
|
+
class SettingsFrame < Frame
|
203
|
+
TYPE = 0x4
|
204
|
+
FORMAT = "nN".freeze
|
205
|
+
|
206
|
+
include Acknowledgement
|
207
|
+
|
208
|
+
def connection?
|
209
|
+
true
|
210
|
+
end
|
211
|
+
|
212
|
+
def unpack
|
213
|
+
super.scan(/....../).map{|s| s.unpack(FORMAT)}
|
214
|
+
end
|
215
|
+
|
216
|
+
def pack(settings = [])
|
217
|
+
super settings.map{|s| s.pack(FORMAT)}.join
|
218
|
+
end
|
219
|
+
|
220
|
+
def apply(connection)
|
221
|
+
connection.receive_settings(self)
|
222
|
+
end
|
223
|
+
|
224
|
+
def read_payload(io)
|
225
|
+
super
|
226
|
+
|
227
|
+
if @stream_id != 0
|
228
|
+
raise ProtocolError, "Settings apply to connection only, but stream_id was given"
|
229
|
+
end
|
230
|
+
|
231
|
+
if acknowledgement? and @length != 0
|
232
|
+
raise FrameSizeError, "Settings acknowledgement must not contain payload: #{@payload.inspect}"
|
233
|
+
end
|
234
|
+
|
235
|
+
if (@length % 6) != 0
|
236
|
+
raise FrameSizeError, "Invalid frame length"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,313 @@
|
|
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
|
+
require_relative 'flow_control'
|
23
|
+
|
24
|
+
module HTTP
|
25
|
+
module Protocol
|
26
|
+
module HTTP2
|
27
|
+
# A single HTTP 2.0 connection can multiplex multiple streams in parallel:
|
28
|
+
# multiple requests and responses can be in flight simultaneously and stream
|
29
|
+
# data can be interleaved and prioritized.
|
30
|
+
#
|
31
|
+
# This class encapsulates all of the state, transition, flow-control, and
|
32
|
+
# error management as defined by the HTTP 2.0 specification. All you have
|
33
|
+
# to do is subscribe to appropriate events (marked with ":" prefix in
|
34
|
+
# diagram below) and provide your application logic to handle request
|
35
|
+
# and response processing.
|
36
|
+
#
|
37
|
+
# +--------+
|
38
|
+
# send PP | | recv PP
|
39
|
+
# ,--------| idle |--------.
|
40
|
+
# / | | \
|
41
|
+
# v +--------+ v
|
42
|
+
# +----------+ | +----------+
|
43
|
+
# | | | send H / | |
|
44
|
+
# ,------| reserved | | recv H | reserved |------.
|
45
|
+
# | | (local) | | | (remote) | |
|
46
|
+
# | +----------+ v +----------+ |
|
47
|
+
# | | +--------+ | |
|
48
|
+
# | | recv ES | | send ES | |
|
49
|
+
# | send H | ,-------| open |-------. | recv H |
|
50
|
+
# | | / | | \ | |
|
51
|
+
# | v v +--------+ v v |
|
52
|
+
# | +----------+ | +----------+ |
|
53
|
+
# | | half | | | half | |
|
54
|
+
# | | closed | | send R / | closed | |
|
55
|
+
# | | (remote) | | recv R | (local) | |
|
56
|
+
# | +----------+ | +----------+ |
|
57
|
+
# | | | | |
|
58
|
+
# | | send ES / | recv ES / | |
|
59
|
+
# | | send R / v send R / | |
|
60
|
+
# | | recv R +--------+ recv R | |
|
61
|
+
# | send R / `----------->| |<-----------' send R / |
|
62
|
+
# | recv R | closed | recv R |
|
63
|
+
# `----------------------->| |<----------------------'
|
64
|
+
# +--------+
|
65
|
+
#
|
66
|
+
# send: endpoint sends this frame
|
67
|
+
# recv: endpoint receives this frame
|
68
|
+
#
|
69
|
+
# H: HEADERS frame (with implied CONTINUATIONs)
|
70
|
+
# PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
|
71
|
+
# ES: END_STREAM flag
|
72
|
+
# R: RST_STREAM frame
|
73
|
+
#
|
74
|
+
class Stream
|
75
|
+
include FlowControl
|
76
|
+
|
77
|
+
def initialize(connection, id = connection.next_stream_id)
|
78
|
+
@connection = connection
|
79
|
+
@id = id
|
80
|
+
|
81
|
+
@connection.streams[@id] = self
|
82
|
+
|
83
|
+
@state = :idle
|
84
|
+
|
85
|
+
@priority = nil
|
86
|
+
@local_window = connection.local_window.dup
|
87
|
+
@remote_window = connection.remote_window.dup
|
88
|
+
|
89
|
+
@headers = nil
|
90
|
+
@data = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Stream ID (odd for client initiated streams, even otherwise).
|
94
|
+
attr :id
|
95
|
+
|
96
|
+
# Stream state as defined by HTTP 2.0.
|
97
|
+
attr :state
|
98
|
+
|
99
|
+
attr :headers
|
100
|
+
attr :data
|
101
|
+
|
102
|
+
attr :local_window
|
103
|
+
attr :remote_window
|
104
|
+
|
105
|
+
def maximum_frame_size
|
106
|
+
@connection.available_frame_size
|
107
|
+
end
|
108
|
+
|
109
|
+
def write_frame(frame)
|
110
|
+
@connection.write_frame(frame)
|
111
|
+
end
|
112
|
+
|
113
|
+
def closed?
|
114
|
+
@state == :closed or @state == :reset
|
115
|
+
end
|
116
|
+
|
117
|
+
def send_headers?
|
118
|
+
@state == :idle or @state == :reseved_local or @state == :open or @state == :half_closed_remote
|
119
|
+
end
|
120
|
+
|
121
|
+
def send_failure(status, reason)
|
122
|
+
if send_headers?
|
123
|
+
send_headers(nil, [
|
124
|
+
[':status', status.to_s],
|
125
|
+
[':reason', reason]
|
126
|
+
], END_STREAM)
|
127
|
+
else
|
128
|
+
send_reset_stream(PROTOCOL_ERROR)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private def write_headers(priority, headers, flags = 0)
|
133
|
+
data = @connection.encode_headers(headers)
|
134
|
+
|
135
|
+
frame = HeadersFrame.new(@id, flags)
|
136
|
+
frame.pack(priority, data, maximum_size: @connection.maximum_frame_size)
|
137
|
+
|
138
|
+
write_frame(frame)
|
139
|
+
|
140
|
+
return frame
|
141
|
+
end
|
142
|
+
|
143
|
+
#The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state.
|
144
|
+
def send_headers(*args)
|
145
|
+
if @state == :idle
|
146
|
+
frame = write_headers(*args)
|
147
|
+
|
148
|
+
if frame.end_stream?
|
149
|
+
@state = :half_closed_local
|
150
|
+
else
|
151
|
+
@state = :open
|
152
|
+
end
|
153
|
+
elsif @state == :reseved_local
|
154
|
+
frame = write_headers(*args)
|
155
|
+
|
156
|
+
@state = :half_closed_remote
|
157
|
+
elsif @state == :open
|
158
|
+
frame = write_headers(*args)
|
159
|
+
|
160
|
+
if frame.end_stream?
|
161
|
+
@state = :half_closed_local
|
162
|
+
end
|
163
|
+
elsif @state == :half_closed_remote
|
164
|
+
frame = write_headers(*args)
|
165
|
+
|
166
|
+
if frame.end_stream?
|
167
|
+
close!
|
168
|
+
end
|
169
|
+
else
|
170
|
+
raise ProtocolError, "Cannot send headers in state: #{@state}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def consume_remote_window(frame)
|
175
|
+
super
|
176
|
+
|
177
|
+
@connection.consume_remote_window(frame)
|
178
|
+
end
|
179
|
+
|
180
|
+
private def write_data(data, flags = 0, **options)
|
181
|
+
frame = DataFrame.new(@id, flags)
|
182
|
+
frame.pack(data, **options)
|
183
|
+
|
184
|
+
# This might fail if the data payload was too big:
|
185
|
+
consume_remote_window(frame)
|
186
|
+
|
187
|
+
write_frame(frame)
|
188
|
+
|
189
|
+
return frame
|
190
|
+
end
|
191
|
+
|
192
|
+
def send_data(*args)
|
193
|
+
if @state == :open
|
194
|
+
frame = write_data(*args)
|
195
|
+
|
196
|
+
if frame.end_stream?
|
197
|
+
@state = :half_closed_local
|
198
|
+
end
|
199
|
+
elsif @state == :half_closed_remote
|
200
|
+
frame = write_data(*args)
|
201
|
+
|
202
|
+
if frame.end_stream?
|
203
|
+
close!
|
204
|
+
end
|
205
|
+
else
|
206
|
+
raise ProtocolError, "Cannot send data in state: #{@state}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def close!(state = :closed)
|
211
|
+
@state = state
|
212
|
+
|
213
|
+
@connection.streams.delete(@id)
|
214
|
+
end
|
215
|
+
|
216
|
+
def send_reset_stream(error_code = 0)
|
217
|
+
if @state != :idle and @state != :closed
|
218
|
+
frame = ResetStreamFrame.new(@id)
|
219
|
+
frame.pack(error_code)
|
220
|
+
|
221
|
+
write_frame(frame)
|
222
|
+
|
223
|
+
close!(:reset)
|
224
|
+
else
|
225
|
+
raise ProtocolError, "Cannot reset stream in state: #{@state}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
private def process_headers(frame)
|
230
|
+
# Receiving request headers:
|
231
|
+
priority, data = frame.unpack
|
232
|
+
|
233
|
+
if priority
|
234
|
+
@priority = priority
|
235
|
+
end
|
236
|
+
|
237
|
+
@connection.decode_headers(data)
|
238
|
+
end
|
239
|
+
|
240
|
+
def receive_headers(frame)
|
241
|
+
if @state == :idle
|
242
|
+
if frame.end_stream?
|
243
|
+
@state = :half_closed_remote
|
244
|
+
else
|
245
|
+
@state = :open
|
246
|
+
end
|
247
|
+
|
248
|
+
@headers = process_headers(frame)
|
249
|
+
elsif @state == :reseved_remote
|
250
|
+
@state = :half_closed_local
|
251
|
+
|
252
|
+
@headers = process_headers(frame)
|
253
|
+
elsif @state == :open
|
254
|
+
if frame.end_stream?
|
255
|
+
@state = :half_closed_remote
|
256
|
+
end
|
257
|
+
|
258
|
+
@headers = process_headers(frame)
|
259
|
+
elsif @state == :half_closed_local
|
260
|
+
if frame.end_stream?
|
261
|
+
close!
|
262
|
+
end
|
263
|
+
|
264
|
+
@headers = process_headers(frame)
|
265
|
+
elsif @state == :reset
|
266
|
+
# ignore...
|
267
|
+
else
|
268
|
+
raise ProtocolError, "Cannot receive headers in state: #{@state}"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# DATA frames are subject to flow control and can only be sent when a stream is in the "open" or "half-closed (remote)" state. The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present. If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, the recipient MUST respond with a stream error of type STREAM_CLOSED.
|
273
|
+
def receive_data(frame)
|
274
|
+
if @state == :open
|
275
|
+
consume_local_window(frame)
|
276
|
+
|
277
|
+
if frame.end_stream?
|
278
|
+
@state = :half_closed_remote
|
279
|
+
end
|
280
|
+
|
281
|
+
@data = frame.unpack
|
282
|
+
elsif @state == :half_closed_local
|
283
|
+
consume_local_window(frame)
|
284
|
+
|
285
|
+
if frame.end_stream?
|
286
|
+
close!
|
287
|
+
end
|
288
|
+
|
289
|
+
@data = frame.unpack
|
290
|
+
elsif @state == :reset
|
291
|
+
# ignore...
|
292
|
+
else
|
293
|
+
raise ProtocolError, "Cannot receive data in state: #{@state}"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def receive_priority(frame)
|
298
|
+
@priority = frame.unpack
|
299
|
+
end
|
300
|
+
|
301
|
+
def receive_reset_stream(frame)
|
302
|
+
if @state != :idle and @state != :closed
|
303
|
+
close!
|
304
|
+
|
305
|
+
return frame.unpack
|
306
|
+
else
|
307
|
+
raise ProtocolError, "Cannot reset stream in state: #{@state}"
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|