http-2 0.11.0 → 1.0.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 +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"
|