http-2 0.11.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -9
- data/lib/http/2/base64.rb +45 -0
- data/lib/http/2/client.rb +19 -6
- data/lib/http/2/connection.rb +235 -163
- data/lib/http/2/emitter.rb +7 -5
- data/lib/http/2/error.rb +24 -1
- data/lib/http/2/extensions.rb +53 -0
- data/lib/http/2/flow_buffer.rb +91 -33
- data/lib/http/2/framer.rb +184 -157
- data/lib/http/2/header/compressor.rb +157 -0
- data/lib/http/2/header/decompressor.rb +144 -0
- data/lib/http/2/header/encoding_context.rb +337 -0
- data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
- data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
- data/lib/http/2/header.rb +35 -0
- data/lib/http/2/server.rb +47 -20
- data/lib/http/2/stream.rb +130 -61
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +14 -13
- data/sig/client.rbs +9 -0
- data/sig/connection.rbs +93 -0
- data/sig/emitter.rbs +13 -0
- data/sig/error.rbs +35 -0
- data/sig/extensions.rbs +5 -0
- data/sig/flow_buffer.rbs +21 -0
- data/sig/frame_buffer.rbs +13 -0
- data/sig/framer.rbs +54 -0
- data/sig/header/compressor.rbs +27 -0
- data/sig/header/decompressor.rbs +22 -0
- data/sig/header/encoding_context.rbs +34 -0
- data/sig/header/huffman.rbs +9 -0
- data/sig/header.rbs +27 -0
- data/sig/next.rbs +101 -0
- data/sig/server.rbs +12 -0
- data/sig/stream.rbs +91 -0
- metadata +38 -79
- data/.autotest +0 -20
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -20
- data/.gitmodules +0 -3
- data/.rspec +0 -5
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -131
- data/.travis.yml +0 -17
- data/Gemfile +0 -16
- data/Guardfile +0 -18
- data/Guardfile.h2spec +0 -12
- data/LICENSE +0 -21
- data/Rakefile +0 -49
- data/example/Gemfile +0 -3
- data/example/README.md +0 -44
- data/example/client.rb +0 -122
- data/example/helper.rb +0 -19
- data/example/keys/server.crt +0 -20
- data/example/keys/server.key +0 -27
- data/example/server.rb +0 -139
- data/example/upgrade_client.rb +0 -153
- data/example/upgrade_server.rb +0 -203
- data/http-2.gemspec +0 -22
- data/lib/http/2/buffer.rb +0 -76
- data/lib/http/2/compressor.rb +0 -572
- data/lib/tasks/generate_huffman_table.rb +0 -166
- data/spec/buffer_spec.rb +0 -28
- data/spec/client_spec.rb +0 -188
- data/spec/compressor_spec.rb +0 -666
- data/spec/connection_spec.rb +0 -681
- data/spec/emitter_spec.rb +0 -54
- data/spec/framer_spec.rb +0 -487
- data/spec/h2spec/h2spec.darwin +0 -0
- data/spec/h2spec/output/non_secure.txt +0 -317
- data/spec/helper.rb +0 -147
- data/spec/hpack_test_spec.rb +0 -84
- data/spec/huffman_spec.rb +0 -68
- data/spec/server_spec.rb +0 -52
- data/spec/stream_spec.rb +0 -878
- data/spec/support/deep_dup.rb +0 -55
- data/spec/support/duplicable.rb +0 -98
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../error"
|
4
|
+
require_relative "../extensions"
|
2
5
|
|
3
6
|
module HTTP2
|
4
7
|
# Implementation of huffman encoding for HPACK
|
@@ -7,7 +10,10 @@ module HTTP2
|
|
7
10
|
module Header
|
8
11
|
# Huffman encoder/decoder
|
9
12
|
class Huffman
|
13
|
+
using StringExtensions
|
14
|
+
|
10
15
|
include Error
|
16
|
+
include PackingExtensions
|
11
17
|
|
12
18
|
BITS_AT_ONCE = 4
|
13
19
|
EOS = 256
|
@@ -20,8 +26,8 @@ module HTTP2
|
|
20
26
|
# @return [String] binary string
|
21
27
|
def encode(str)
|
22
28
|
bitstring = str.each_byte.map { |chr| ENCODE_TABLE[chr] }.join
|
23
|
-
bitstring <<
|
24
|
-
[bitstring].pack(
|
29
|
+
bitstring << ("1" * ((8 - bitstring.size) % 8))
|
30
|
+
[bitstring].pack("B*")
|
25
31
|
end
|
26
32
|
|
27
33
|
# Decodes provided Huffman coded string.
|
@@ -30,12 +36,12 @@ module HTTP2
|
|
30
36
|
# @return [String] binary string
|
31
37
|
# @raise [CompressionError] when Huffman coded string is malformed
|
32
38
|
def decode(buf)
|
33
|
-
emit =
|
39
|
+
emit = "".b
|
34
40
|
state = 0 # start state
|
35
41
|
|
36
42
|
mask = (1 << BITS_AT_ONCE) - 1
|
37
43
|
buf.each_byte do |chr|
|
38
|
-
(8 / BITS_AT_ONCE - 1).downto(0) do |shift|
|
44
|
+
((8 / BITS_AT_ONCE) - 1).downto(0) do |shift|
|
39
45
|
branch = (chr >> (shift * BITS_AT_ONCE)) & mask
|
40
46
|
# MACHINE[state] = [final, [transitions]]
|
41
47
|
# [final] unfinished bits so far are prefix of the EOS code.
|
@@ -43,15 +49,15 @@ module HTTP2
|
|
43
49
|
# [emit] character to be emitted on this transition, empty string, or EOS.
|
44
50
|
# [next] next state number.
|
45
51
|
trans = MACHINE[state][branch]
|
46
|
-
|
52
|
+
raise CompressionError, "Huffman decode error (EOS found)" if trans.first == EOS
|
53
|
+
|
47
54
|
emit << trans.first.chr if trans.first
|
48
55
|
state = trans.last
|
49
56
|
end
|
50
57
|
end
|
51
58
|
# Check whether partial input is correctly filled
|
52
|
-
unless state <= MAX_FINAL_STATE
|
53
|
-
|
54
|
-
end
|
59
|
+
raise CompressionError, "Huffman decode error (EOS invalid)" unless state <= MAX_FINAL_STATE
|
60
|
+
|
55
61
|
emit.force_encoding(Encoding::BINARY)
|
56
62
|
end
|
57
63
|
|
@@ -153,23 +159,23 @@ module HTTP2
|
|
153
159
|
[0x7fff0, 19],
|
154
160
|
[0x1ffc, 13],
|
155
161
|
[0x3ffc, 14],
|
156
|
-
[0x22,
|
162
|
+
[0x22, 6],
|
157
163
|
[0x7ffd, 15],
|
158
|
-
[0x3,
|
159
|
-
[0x23,
|
160
|
-
[0x4,
|
161
|
-
[0x24,
|
162
|
-
[0x5,
|
164
|
+
[0x3, 5],
|
165
|
+
[0x23, 6],
|
166
|
+
[0x4, 5],
|
167
|
+
[0x24, 6],
|
168
|
+
[0x5, 5],
|
163
169
|
[0x25, 6],
|
164
170
|
[0x26, 6],
|
165
171
|
[0x27, 6],
|
166
|
-
[0x6,
|
172
|
+
[0x6, 5],
|
167
173
|
[0x74, 7],
|
168
174
|
[0x75, 7],
|
169
175
|
[0x28, 6],
|
170
176
|
[0x29, 6],
|
171
177
|
[0x2a, 6],
|
172
|
-
[0x7,
|
178
|
+
[0x7, 5],
|
173
179
|
[0x2b, 6],
|
174
180
|
[0x76, 7],
|
175
181
|
[0x2c, 6],
|
@@ -314,10 +320,10 @@ module HTTP2
|
|
314
320
|
[0x7ffffef, 27],
|
315
321
|
[0x7fffff0, 27],
|
316
322
|
[0x3ffffee, 26],
|
317
|
-
[0x3fffffff, 30]
|
323
|
+
[0x3fffffff, 30]
|
318
324
|
].each(&:freeze).freeze
|
319
325
|
|
320
|
-
ENCODE_TABLE = CODES.map { |c, l| [c].pack(
|
326
|
+
ENCODE_TABLE = CODES.map { |c, l| [c].pack("N").unpack1("B*")[-l..-1] }.each(&:freeze).freeze
|
321
327
|
end
|
322
328
|
end
|
323
329
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2
|
4
|
+
# Implementation of header compression for HTTP 2.0 (HPACK) format adapted
|
5
|
+
# to efficiently represent HTTP headers in the context of HTTP 2.0.
|
6
|
+
#
|
7
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
|
8
|
+
module Header
|
9
|
+
# Header representation as defined by the spec.
|
10
|
+
HEADREP = {
|
11
|
+
indexed: { prefix: 7, pattern: 0x80 },
|
12
|
+
incremental: { prefix: 6, pattern: 0x40 },
|
13
|
+
noindex: { prefix: 4, pattern: 0x00 },
|
14
|
+
neverindexed: { prefix: 4, pattern: 0x10 },
|
15
|
+
changetablesize: { prefix: 5, pattern: 0x20 }
|
16
|
+
}.each_value(&:freeze).freeze
|
17
|
+
|
18
|
+
# Predefined options set for Compressor
|
19
|
+
# http://mew.org/~kazu/material/2014-hpack.pdf
|
20
|
+
NAIVE = { index: :never, huffman: :never }.freeze
|
21
|
+
LINEAR = { index: :all, huffman: :never }.freeze
|
22
|
+
STATIC = { index: :static, huffman: :never }.freeze
|
23
|
+
SHORTER = { index: :all, huffman: :never }.freeze
|
24
|
+
NAIVEH = { index: :never, huffman: :always }.freeze
|
25
|
+
LINEARH = { index: :all, huffman: :always }.freeze
|
26
|
+
STATICH = { index: :static, huffman: :always }.freeze
|
27
|
+
SHORTERH = { index: :all, huffman: :shorter }.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require "http/2/header/huffman"
|
32
|
+
require "http/2/header/huffman_statemachine"
|
33
|
+
require "http/2/header/encoding_context"
|
34
|
+
require "http/2/header/compressor"
|
35
|
+
require "http/2/header/decompressor"
|
data/lib/http/2/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module HTTP2
|
3
4
|
# HTTP 2.0 server connection class that implements appropriate header
|
4
5
|
# compression / decompression algorithms and stream management logic.
|
@@ -20,14 +21,19 @@ module HTTP2
|
|
20
21
|
# end
|
21
22
|
#
|
22
23
|
class Server < Connection
|
24
|
+
attr_reader :origin_set
|
25
|
+
|
23
26
|
# Initialize new HTTP 2.0 server object.
|
24
|
-
def initialize(
|
27
|
+
def initialize(settings = {})
|
25
28
|
@stream_id = 2
|
26
29
|
@state = :waiting_magic
|
27
30
|
|
28
31
|
@local_role = :server
|
29
32
|
@remote_role = :client
|
30
33
|
|
34
|
+
@origin_set = []
|
35
|
+
@origins_sent = true
|
36
|
+
|
31
37
|
super
|
32
38
|
end
|
33
39
|
|
@@ -72,14 +78,17 @@ module HTTP2
|
|
72
78
|
receive(CONNECTION_PREFACE_MAGIC)
|
73
79
|
|
74
80
|
# Process received HTTP2-Settings payload
|
75
|
-
buf =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
+
buf = "".b
|
82
|
+
buf << Base64.urlsafe_decode64(settings.to_s)
|
83
|
+
@framer.common_header(
|
84
|
+
{
|
85
|
+
length: buf.bytesize,
|
86
|
+
type: :settings,
|
87
|
+
stream: 0,
|
88
|
+
flags: []
|
89
|
+
},
|
90
|
+
buffer: buf
|
81
91
|
)
|
82
|
-
buf.prepend(header)
|
83
92
|
receive(buf)
|
84
93
|
|
85
94
|
# Activate stream (id: 1) with on HTTP/1.1 request parameters
|
@@ -87,16 +96,17 @@ module HTTP2
|
|
87
96
|
emit(:stream, stream)
|
88
97
|
|
89
98
|
headers_frame = {
|
90
|
-
type:
|
91
|
-
|
92
|
-
|
99
|
+
type: :headers,
|
100
|
+
flags: [:end_headers],
|
101
|
+
stream: 1,
|
102
|
+
weight: DEFAULT_WEIGHT,
|
93
103
|
dependency: 0,
|
94
|
-
exclusive:
|
95
|
-
payload: headers
|
104
|
+
exclusive: false,
|
105
|
+
payload: headers
|
96
106
|
}
|
97
107
|
|
98
108
|
if body.empty?
|
99
|
-
headers_frame
|
109
|
+
headers_frame[:flags] << [:end_stream]
|
100
110
|
stream << headers_frame
|
101
111
|
else
|
102
112
|
stream << headers_frame
|
@@ -110,24 +120,41 @@ module HTTP2
|
|
110
120
|
@state = :waiting_magic
|
111
121
|
end
|
112
122
|
|
123
|
+
def origin_set=(origins)
|
124
|
+
@origin_set = Array(origins).map(&:to_s)
|
125
|
+
@origins_sent = @origin_set.empty?
|
126
|
+
end
|
127
|
+
|
113
128
|
private
|
114
129
|
|
130
|
+
def connection_settings(frame)
|
131
|
+
super
|
132
|
+
return unless frame[:flags].include?(:ack) && !@origins_sent
|
133
|
+
|
134
|
+
send(type: :origin, stream: 0, payload: @origin_set)
|
135
|
+
end
|
136
|
+
|
137
|
+
def verify_pseudo_headers(frame)
|
138
|
+
_verify_pseudo_headers(frame, REQUEST_MANDATORY_HEADERS)
|
139
|
+
end
|
140
|
+
|
115
141
|
# Handle locally initiated server-push event emitted by the stream.
|
116
142
|
#
|
117
|
-
# @param
|
143
|
+
# @param parent [Stream]
|
144
|
+
# @param headers [Enumerable[String, String]]
|
145
|
+
# @param flags [Array[Symbol]]
|
118
146
|
# @param callback [Proc]
|
119
|
-
def promise(
|
120
|
-
parent, headers, flags = *args
|
147
|
+
def promise(parent, headers, flags)
|
121
148
|
promise = new_stream(parent: parent)
|
122
149
|
promise.send(
|
123
150
|
type: :push_promise,
|
124
151
|
flags: flags,
|
125
152
|
stream: parent.id,
|
126
153
|
promise_stream: promise.id,
|
127
|
-
payload: headers.to_a
|
154
|
+
payload: headers.to_a
|
128
155
|
)
|
129
156
|
|
130
|
-
|
157
|
+
yield(promise)
|
131
158
|
end
|
132
159
|
end
|
133
160
|
end
|
data/lib/http/2/stream.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# A single HTTP 2.0 connection can multiplex multiple streams in parallel:
|
3
5
|
# multiple requests and responses can be in flight simultaneously and stream
|
@@ -49,11 +51,10 @@ module HTTP2
|
|
49
51
|
|
50
52
|
# Stream priority as set by initiator.
|
51
53
|
attr_reader :weight
|
52
|
-
attr_reader :dependency
|
54
|
+
attr_reader :dependency, :remote_window
|
53
55
|
|
54
56
|
# Size of current stream flow control window.
|
55
57
|
attr_reader :local_window
|
56
|
-
attr_reader :remote_window
|
57
58
|
alias window local_window
|
58
59
|
|
59
60
|
# Reason why connection was closed.
|
@@ -73,19 +74,24 @@ module HTTP2
|
|
73
74
|
# @param parent [Stream]
|
74
75
|
# @param state [Symbol]
|
75
76
|
def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
|
77
|
+
stream_error(:protocol_error, msg: "stream can't depend on itself") if id == dependency
|
78
|
+
|
76
79
|
@connection = connection
|
77
80
|
@id = id
|
78
81
|
@weight = weight
|
79
82
|
@dependency = dependency
|
80
|
-
process_priority(weight: weight,
|
83
|
+
process_priority(weight: weight, dependency: dependency, exclusive: exclusive)
|
81
84
|
@local_window_max_size = connection.local_settings[:settings_initial_window_size]
|
82
|
-
@local_window
|
85
|
+
@local_window = connection.local_settings[:settings_initial_window_size]
|
83
86
|
@remote_window = connection.remote_settings[:settings_initial_window_size]
|
84
87
|
@parent = parent
|
85
88
|
@state = state
|
86
89
|
@error = false
|
87
90
|
@closed = false
|
88
|
-
@
|
91
|
+
@_method = @_content_length = @_status_code = nil
|
92
|
+
@_waiting_on_trailers = false
|
93
|
+
@received_data = false
|
94
|
+
@activated = false
|
89
95
|
|
90
96
|
on(:window) { |v| @remote_window = v }
|
91
97
|
on(:local_window) { |v| @local_window_max_size = @local_window = v }
|
@@ -103,26 +109,52 @@ module HTTP2
|
|
103
109
|
|
104
110
|
case frame[:type]
|
105
111
|
when :data
|
112
|
+
# 6.1. DATA
|
113
|
+
# If a DATA frame is received whose stream is not in "open" or
|
114
|
+
# "half closed (local)" state, the recipient MUST respond with a
|
115
|
+
# stream error (Section 5.4.2) of type STREAM_CLOSED.
|
116
|
+
stream_error(:stream_closed) unless @state == :open ||
|
117
|
+
@state == :half_closed_local ||
|
118
|
+
@state == :half_closing || @state == :closing ||
|
119
|
+
(@state == :closed && @closed == :local_rst)
|
120
|
+
@received_data = true
|
121
|
+
calculate_content_length(frame[:length])
|
106
122
|
update_local_window(frame)
|
107
123
|
# Emit DATA frame
|
108
124
|
emit(:data, frame[:payload]) unless frame[:ignore]
|
109
125
|
calculate_window_update(@local_window_max_size)
|
110
126
|
when :headers
|
127
|
+
stream_error(:stream_closed) if (@state == :closed && @closed != :local_rst) ||
|
128
|
+
@state == :remote_closed
|
129
|
+
@_method ||= frame[:method]
|
130
|
+
@_status_code ||= frame[:status]
|
131
|
+
@_content_length ||= frame[:content_length]
|
132
|
+
@_trailers ||= frame[:trailer]
|
133
|
+
if @_waiting_on_trailers ||
|
134
|
+
(@received_data &&
|
135
|
+
(!@_status_code || @_status_code >= 200))
|
136
|
+
|
137
|
+
# An endpoint that receives a HEADERS frame without the END_STREAM flag set after receiving a final
|
138
|
+
# (non-informational) status code MUST treat the corresponding request or response as malformed.
|
139
|
+
verify_trailers(frame)
|
140
|
+
end
|
111
141
|
emit(:headers, frame[:payload]) unless frame[:ignore]
|
142
|
+
@_waiting_on_trailers = !@_trailers.nil?
|
112
143
|
when :push_promise
|
113
144
|
emit(:promise_headers, frame[:payload]) unless frame[:ignore]
|
145
|
+
when :continuation
|
146
|
+
stream_error(:stream_closed) if (@state == :closed && @closed != :local_rst) || @state == :remote_closed
|
147
|
+
stream_error(:protocol_error) if @received_data
|
114
148
|
when :priority
|
115
149
|
process_priority(frame)
|
116
150
|
when :window_update
|
117
|
-
process_window_update(frame)
|
151
|
+
process_window_update(frame: frame)
|
118
152
|
when :altsvc
|
119
153
|
# 4. The ALTSVC HTTP/2 Frame
|
120
154
|
# An ALTSVC frame on a
|
121
155
|
# stream other than stream 0 containing non-empty "Origin" information
|
122
156
|
# is invalid and MUST be ignored.
|
123
|
-
if !frame[:origin] || frame[:origin].empty?
|
124
|
-
emit(frame[:type], frame)
|
125
|
-
end
|
157
|
+
emit(frame[:type], frame) if !frame[:origin] || frame[:origin].empty?
|
126
158
|
when :blocked
|
127
159
|
emit(frame[:type], frame)
|
128
160
|
end
|
@@ -131,6 +163,29 @@ module HTTP2
|
|
131
163
|
end
|
132
164
|
alias << receive
|
133
165
|
|
166
|
+
def verify_trailers(frame)
|
167
|
+
stream_error(:protocol_error, msg: "trailer headers frame must close the stream") unless end_stream?(frame)
|
168
|
+
return unless @_trailers
|
169
|
+
|
170
|
+
trailers = frame[:payload]
|
171
|
+
return unless trailers.respond_to?(:each)
|
172
|
+
|
173
|
+
trailers.each do |field, _| # rubocop:disable Style/HashEachMethods
|
174
|
+
@_trailers.delete(field)
|
175
|
+
break if @_trailers.empty?
|
176
|
+
end
|
177
|
+
stream_error(:protocol_error, msg: "didn't receive all expected trailer headers") unless @_trailers.empty?
|
178
|
+
end
|
179
|
+
|
180
|
+
def calculate_content_length(data_length)
|
181
|
+
return unless @_content_length && data_length
|
182
|
+
|
183
|
+
@_content_length -= data_length
|
184
|
+
return if @_content_length >= 0
|
185
|
+
|
186
|
+
stream_error(:protocol_error, msg: "received more data than what was defined in content-length")
|
187
|
+
end
|
188
|
+
|
134
189
|
# Processes outgoing HTTP 2.0 frames. Data frames may be automatically
|
135
190
|
# split and buffered based on maximum frame size and current stream flow
|
136
191
|
# control window size.
|
@@ -164,13 +219,13 @@ module HTTP2
|
|
164
219
|
def headers(headers, end_headers: true, end_stream: false)
|
165
220
|
flags = []
|
166
221
|
flags << :end_headers if end_headers
|
167
|
-
flags << :end_stream if end_stream
|
222
|
+
flags << :end_stream if end_stream || @_method == "HEAD"
|
168
223
|
|
169
224
|
send(type: :headers, flags: flags, payload: headers)
|
170
225
|
end
|
171
226
|
|
172
227
|
def promise(headers, end_headers: true, &block)
|
173
|
-
|
228
|
+
raise ArgumentError, "must provide callback" unless block
|
174
229
|
|
175
230
|
flags = end_headers ? [:end_headers] : []
|
176
231
|
emit(:promise, self, headers, flags, &block)
|
@@ -183,7 +238,7 @@ module HTTP2
|
|
183
238
|
# @param dependency [Integer] new stream dependency stream
|
184
239
|
def reprioritize(weight: 16, dependency: 0, exclusive: false)
|
185
240
|
stream_error if @id.even?
|
186
|
-
send(type: :priority, weight: weight,
|
241
|
+
send(type: :priority, weight: weight, dependency: dependency, exclusive: exclusive)
|
187
242
|
end
|
188
243
|
|
189
244
|
# Sends DATA frame containing response payload.
|
@@ -243,6 +298,7 @@ module HTTP2
|
|
243
298
|
def window_update(increment)
|
244
299
|
# emit stream-level WINDOW_UPDATE unless stream is closed
|
245
300
|
return if @state == :closed || @state == :remote_closed
|
301
|
+
|
246
302
|
send(type: :window_update, increment: increment)
|
247
303
|
end
|
248
304
|
|
@@ -345,18 +401,18 @@ module HTTP2
|
|
345
401
|
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
346
402
|
when :reserved_local
|
347
403
|
@state = if sending
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
404
|
+
case frame[:type]
|
405
|
+
when :headers then event(:half_closed_remote)
|
406
|
+
when :rst_stream then event(:local_rst)
|
407
|
+
else stream_error
|
408
|
+
end
|
409
|
+
else
|
410
|
+
case frame[:type]
|
411
|
+
when :rst_stream then event(:remote_rst)
|
412
|
+
when :priority, :window_update then @state
|
413
|
+
else stream_error
|
414
|
+
end
|
415
|
+
end
|
360
416
|
|
361
417
|
# A stream in the "reserved (remote)" state has been reserved by a
|
362
418
|
# remote peer.
|
@@ -374,18 +430,18 @@ module HTTP2
|
|
374
430
|
# error (Section 5.4.1) of type PROTOCOL_ERROR.
|
375
431
|
when :reserved_remote
|
376
432
|
@state = if sending
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
433
|
+
case frame[:type]
|
434
|
+
when :rst_stream then event(:local_rst)
|
435
|
+
when :priority, :window_update then @state
|
436
|
+
else stream_error
|
437
|
+
end
|
438
|
+
else
|
439
|
+
case frame[:type]
|
440
|
+
when :headers then event(:half_closed_local)
|
441
|
+
when :rst_stream then event(:remote_rst)
|
442
|
+
else stream_error
|
443
|
+
end
|
444
|
+
end
|
389
445
|
|
390
446
|
# A stream in the "open" state may be used by both peers to send
|
391
447
|
# frames of any type. In this state, sending peers observe
|
@@ -523,26 +579,24 @@ module HTTP2
|
|
523
579
|
when :closed
|
524
580
|
if sending
|
525
581
|
case frame[:type]
|
526
|
-
when :rst_stream
|
527
|
-
when :priority
|
582
|
+
when :rst_stream # ignore
|
583
|
+
when :priority
|
528
584
|
process_priority(frame)
|
529
585
|
else
|
530
|
-
stream_error(:stream_closed) unless
|
586
|
+
stream_error(:stream_closed) unless frame[:type] == :rst_stream
|
531
587
|
end
|
588
|
+
elsif frame[:type] == :priority
|
589
|
+
process_priority(frame)
|
532
590
|
else
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
when :rst_stream, :window_update # nop here
|
540
|
-
else
|
541
|
-
stream_error(:stream_closed)
|
542
|
-
end
|
543
|
-
when :local_rst, :local_closed
|
544
|
-
frame[:ignore] = true if frame[:type] != :window_update
|
591
|
+
case @closed
|
592
|
+
when :remote_rst, :remote_closed
|
593
|
+
case frame[:type]
|
594
|
+
when :rst_stream, :window_update # nop here
|
595
|
+
else
|
596
|
+
stream_error(:stream_closed)
|
545
597
|
end
|
598
|
+
when :local_rst, :local_closed
|
599
|
+
frame[:ignore] = true if frame[:type] != :window_update
|
546
600
|
end
|
547
601
|
end
|
548
602
|
end
|
@@ -552,7 +606,7 @@ module HTTP2
|
|
552
606
|
case newstate
|
553
607
|
when :open
|
554
608
|
@state = newstate
|
555
|
-
|
609
|
+
activate_stream_in_conn
|
556
610
|
|
557
611
|
when :reserved_local, :reserved_remote
|
558
612
|
@state = newstate
|
@@ -560,7 +614,7 @@ module HTTP2
|
|
560
614
|
|
561
615
|
when :half_closed_local, :half_closed_remote
|
562
616
|
@closed = newstate
|
563
|
-
|
617
|
+
activate_stream_in_conn unless @state == :open
|
564
618
|
@state = :half_closing
|
565
619
|
|
566
620
|
when :local_closed, :remote_closed, :local_rst, :remote_rst
|
@@ -571,11 +625,25 @@ module HTTP2
|
|
571
625
|
@state
|
572
626
|
end
|
573
627
|
|
628
|
+
# Streams that are in the "open" state, or either of the "half closed"
|
629
|
+
# states count toward the maximum number of streams that an endpoint is
|
630
|
+
# permitted to open.
|
631
|
+
def activate_stream_in_conn
|
632
|
+
@connection.active_stream_count += 1
|
633
|
+
@activated = true
|
634
|
+
emit(:active)
|
635
|
+
end
|
636
|
+
|
637
|
+
def close_stream_in_conn(*args)
|
638
|
+
@connection.active_stream_count -= 1 if @activated
|
639
|
+
emit(:close, *args)
|
640
|
+
end
|
641
|
+
|
574
642
|
def complete_transition(frame)
|
575
643
|
case @state
|
576
644
|
when :closing
|
577
645
|
@state = :closed
|
578
|
-
|
646
|
+
close_stream_in_conn(frame[:error])
|
579
647
|
when :half_closing
|
580
648
|
@state = @closed
|
581
649
|
emit(:half_close)
|
@@ -584,12 +652,12 @@ module HTTP2
|
|
584
652
|
|
585
653
|
def process_priority(frame)
|
586
654
|
@weight = frame[:weight]
|
587
|
-
@dependency = frame[:
|
655
|
+
@dependency = frame[:dependency]
|
588
656
|
emit(
|
589
657
|
:priority,
|
590
|
-
weight:
|
591
|
-
dependency: frame[:
|
592
|
-
exclusive:
|
658
|
+
weight: frame[:weight],
|
659
|
+
dependency: frame[:dependency],
|
660
|
+
exclusive: frame[:exclusive]
|
593
661
|
)
|
594
662
|
# TODO: implement dependency tree housekeeping
|
595
663
|
# Latest draft defines a fairly complex priority control.
|
@@ -601,17 +669,18 @@ module HTTP2
|
|
601
669
|
def end_stream?(frame)
|
602
670
|
case frame[:type]
|
603
671
|
when :data, :headers, :continuation
|
604
|
-
frame[:flags].include?(:end_stream)
|
672
|
+
frame[:flags] && frame[:flags].include?(:end_stream)
|
605
673
|
else false
|
606
674
|
end
|
607
675
|
end
|
608
676
|
|
609
677
|
def stream_error(error = :internal_error, msg: nil)
|
678
|
+
# if the stream already broke with an error, ignore subsequent
|
679
|
+
|
610
680
|
@error = error
|
611
681
|
close(error) if @state != :closed
|
612
682
|
|
613
|
-
|
614
|
-
fail Error.const_get(klass), msg
|
683
|
+
raise Error.types[error], msg
|
615
684
|
end
|
616
685
|
alias error stream_error
|
617
686
|
|
data/lib/http/2/version.rb
CHANGED
data/lib/http/2.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "http/2/version"
|
4
|
+
require "http/2/extensions"
|
5
|
+
require "http/2/base64"
|
6
|
+
require "http/2/error"
|
7
|
+
require "http/2/emitter"
|
8
|
+
require "http/2/flow_buffer"
|
9
|
+
require "http/2/header"
|
10
|
+
require "http/2/framer"
|
11
|
+
require "http/2/connection"
|
12
|
+
require "http/2/client"
|
13
|
+
require "http/2/server"
|
14
|
+
require "http/2/stream"
|