http-2 0.11.0 → 0.12.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 +0 -2
- data/lib/http/2/buffer.rb +6 -4
- data/lib/http/2/client.rb +5 -1
- data/lib/http/2/compressor.rb +42 -34
- data/lib/http/2/connection.rb +72 -86
- data/lib/http/2/emitter.rb +4 -1
- data/lib/http/2/error.rb +2 -0
- data/lib/http/2/flow_buffer.rb +8 -3
- data/lib/http/2/framer.rb +83 -94
- data/lib/http/2/huffman.rb +19 -17
- data/lib/http/2/server.rb +9 -7
- data/lib/http/2/stream.rb +48 -48
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +2 -0
- metadata +7 -60
- 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/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/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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1788e5559bb1f36b98896322d1c846ef73b0fba4a7a45b524845735deacb9aa
|
4
|
+
data.tar.gz: 62ea957139ea576aa4553303f6db141cd9297a8ce75624553e79a90a69f74717
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb252eeff936187cf2ebebdf93e69fb6f82dba50a0fece880f40a8235b790165ad5d3f167b0c3948adf040388fc7cd49bc1bd1a24b754447c83dd2020be78eaa
|
7
|
+
data.tar.gz: 0657b1b61f526041167473faf29e50a0cbf4bbfcbac80a458a98a205ab5a01f409da7d62010e5620e5e5fe6cb6e7d1bd5a2ae2a831fcac7da1c1ab2192511c1e
|
data/README.md
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
# HTTP-2
|
2
2
|
|
3
3
|
[](http://rubygems.org/gems/http-2)
|
4
|
-
[](https://travis-ci.org/igrigorik/http-2)
|
5
4
|
[](https://coveralls.io/r/igrigorik/http-2)
|
6
|
-
[](https://github.com/igrigorik/ga-beacon)
|
7
5
|
|
8
6
|
Pure Ruby, framework and transport agnostic, implementation of HTTP/2 protocol and HPACK header compression with support for:
|
9
7
|
|
data/lib/http/2/buffer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
4
|
|
3
5
|
module HTTP2
|
@@ -6,11 +8,11 @@ module HTTP2
|
|
6
8
|
class Buffer
|
7
9
|
extend Forwardable
|
8
10
|
|
9
|
-
def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
|
11
|
+
def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack, :unpack1,
|
10
12
|
:size, :each_byte, :to_str, :to_s, :length, :inspect,
|
11
13
|
:[], :[]=, :empty?, :bytesize, :include?
|
12
14
|
|
13
|
-
UINT32 = 'N'
|
15
|
+
UINT32 = 'N'
|
14
16
|
private_constant :UINT32
|
15
17
|
|
16
18
|
# Forces binary encoding on the string
|
@@ -59,12 +61,12 @@ module HTTP2
|
|
59
61
|
# Slice unsigned 32-bit integer from buffer.
|
60
62
|
# @return [Integer]
|
61
63
|
def read_uint32
|
62
|
-
read(4).
|
64
|
+
read(4).unpack1(UINT32)
|
63
65
|
end
|
64
66
|
|
65
67
|
# Ensures that data that is added is binary encoded as well,
|
66
68
|
# otherwise this could lead to the Buffer instance changing its encoding.
|
67
|
-
[
|
69
|
+
%i[<< prepend].each do |mutating_method|
|
68
70
|
define_method(mutating_method) do |string|
|
69
71
|
string = string.dup if string.frozen?
|
70
72
|
@buffer.send mutating_method, string.force_encoding(Encoding::BINARY)
|
data/lib/http/2/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# HTTP 2.0 client connection class that implements appropriate header
|
3
5
|
# compression / decompression algorithms and stream management logic.
|
@@ -45,7 +47,8 @@ module HTTP2
|
|
45
47
|
|
46
48
|
# sends the preface and initializes the first stream in half-closed state
|
47
49
|
def upgrade
|
48
|
-
|
50
|
+
raise ProtocolError unless @stream_id == 1
|
51
|
+
|
49
52
|
send_connection_preface
|
50
53
|
new_stream(state: :half_closed_local)
|
51
54
|
end
|
@@ -53,6 +56,7 @@ module HTTP2
|
|
53
56
|
# Emit the connection preface if not yet
|
54
57
|
def send_connection_preface
|
55
58
|
return unless @state == :waiting_connection_preface
|
59
|
+
|
56
60
|
@state = :connected
|
57
61
|
emit(:frame, CONNECTION_PREFACE_MAGIC)
|
58
62
|
|
data/lib/http/2/compressor.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# Implementation of header compression for HTTP 2.0 (HPACK) format adapted
|
3
5
|
# to efficiently represent HTTP headers in the context of HTTP 2.0.
|
@@ -74,7 +76,7 @@ module HTTP2
|
|
74
76
|
['user-agent', ''],
|
75
77
|
['vary', ''],
|
76
78
|
['via', ''],
|
77
|
-
['www-authenticate', '']
|
79
|
+
['www-authenticate', '']
|
78
80
|
].each { |pair| pair.each(&:freeze).freeze }.freeze
|
79
81
|
|
80
82
|
# Current table of header key-value pairs.
|
@@ -96,9 +98,9 @@ module HTTP2
|
|
96
98
|
# :index Symbol :all, :static, :never
|
97
99
|
def initialize(**options)
|
98
100
|
default_options = {
|
99
|
-
huffman:
|
100
|
-
index:
|
101
|
-
table_size: 4096
|
101
|
+
huffman: :shorter,
|
102
|
+
index: :all,
|
103
|
+
table_size: 4096
|
102
104
|
}
|
103
105
|
@table = []
|
104
106
|
@options = default_options.merge(options)
|
@@ -112,7 +114,7 @@ module HTTP2
|
|
112
114
|
t = @table
|
113
115
|
l = @limit
|
114
116
|
other.instance_eval do
|
115
|
-
@table = t.dup
|
117
|
+
@table = t.dup # shallow copy
|
116
118
|
@limit = l
|
117
119
|
end
|
118
120
|
other
|
@@ -131,7 +133,8 @@ module HTTP2
|
|
131
133
|
def dereference(index)
|
132
134
|
# NOTE: index is zero-based in this module.
|
133
135
|
value = STATIC_TABLE[index] || @table[index - STATIC_TABLE.size]
|
134
|
-
|
136
|
+
raise CompressionError, 'Index too large' unless value
|
137
|
+
|
135
138
|
value
|
136
139
|
end
|
137
140
|
|
@@ -146,9 +149,8 @@ module HTTP2
|
|
146
149
|
|
147
150
|
case cmd[:type]
|
148
151
|
when :changetablesize
|
149
|
-
if cmd[:value] > @limit
|
150
|
-
|
151
|
-
end
|
152
|
+
raise CompressionError, 'dynamic table size update exceed limit' if cmd[:value] > @limit
|
153
|
+
|
152
154
|
self.table_size = cmd[:value]
|
153
155
|
|
154
156
|
when :indexed
|
@@ -186,7 +188,7 @@ module HTTP2
|
|
186
188
|
add_to_table(emit) if cmd[:type] == :incremental
|
187
189
|
|
188
190
|
else
|
189
|
-
|
191
|
+
raise CompressionError, "Invalid type: #{cmd[:type]}"
|
190
192
|
end
|
191
193
|
|
192
194
|
emit
|
@@ -202,7 +204,7 @@ module HTTP2
|
|
202
204
|
def encode(headers)
|
203
205
|
commands = []
|
204
206
|
# Literals commands are marked with :noindex when index is not used
|
205
|
-
noindex = [
|
207
|
+
noindex = %i[static never].include?(@options[:index])
|
206
208
|
headers.each do |field, value|
|
207
209
|
# Literal header names MUST be translated to lowercase before
|
208
210
|
# encoding and transmission.
|
@@ -232,7 +234,7 @@ module HTTP2
|
|
232
234
|
exact = nil
|
233
235
|
name_only = nil
|
234
236
|
|
235
|
-
if [
|
237
|
+
if %i[all static].include?(@options[:index])
|
236
238
|
STATIC_TABLE.each_index do |i|
|
237
239
|
if STATIC_TABLE[i] == header
|
238
240
|
exact ||= i
|
@@ -284,6 +286,7 @@ module HTTP2
|
|
284
286
|
# @param cmd [Array] +[name, value]+
|
285
287
|
def add_to_table(cmd)
|
286
288
|
return unless size_check(cmd)
|
289
|
+
|
287
290
|
@table.unshift(cmd)
|
288
291
|
end
|
289
292
|
|
@@ -309,11 +312,11 @@ module HTTP2
|
|
309
312
|
|
310
313
|
# Header representation as defined by the spec.
|
311
314
|
HEADREP = {
|
312
|
-
indexed:
|
313
|
-
incremental:
|
314
|
-
noindex:
|
315
|
+
indexed: { prefix: 7, pattern: 0x80 },
|
316
|
+
incremental: { prefix: 6, pattern: 0x40 },
|
317
|
+
noindex: { prefix: 4, pattern: 0x00 },
|
315
318
|
neverindexed: { prefix: 4, pattern: 0x10 },
|
316
|
-
changetablesize: { prefix: 5, pattern: 0x20 }
|
319
|
+
changetablesize: { prefix: 5, pattern: 0x20 }
|
317
320
|
}.each_value(&:freeze).freeze
|
318
321
|
|
319
322
|
# Predefined options set for Compressor
|
@@ -330,6 +333,7 @@ module HTTP2
|
|
330
333
|
# Responsible for encoding header key-value pairs using HPACK algorithm.
|
331
334
|
class Compressor
|
332
335
|
# @param options [Hash] encoding options
|
336
|
+
# @see EncodingContext#initialize
|
333
337
|
def initialize(**options)
|
334
338
|
@cc = EncodingContext.new(**options)
|
335
339
|
end
|
@@ -356,14 +360,14 @@ module HTTP2
|
|
356
360
|
# @param n [Integer] number of available bits
|
357
361
|
# @return [String] binary string
|
358
362
|
def integer(i, n)
|
359
|
-
limit = 2**n - 1
|
363
|
+
limit = (2**n) - 1
|
360
364
|
return [i].pack('C') if i < limit
|
361
365
|
|
362
366
|
bytes = []
|
363
367
|
bytes.push limit unless n.zero?
|
364
368
|
|
365
369
|
i -= limit
|
366
|
-
while
|
370
|
+
while i >= 128
|
367
371
|
bytes.push((i % 128) + 128)
|
368
372
|
i /= 128
|
369
373
|
end
|
@@ -393,7 +397,8 @@ module HTTP2
|
|
393
397
|
# @param str [String]
|
394
398
|
# @return [String] binary string
|
395
399
|
def string(str)
|
396
|
-
plain
|
400
|
+
plain = nil
|
401
|
+
huffman = nil
|
397
402
|
unless @cc.options[:huffman] == :always
|
398
403
|
plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY)
|
399
404
|
end
|
@@ -463,12 +468,9 @@ module HTTP2
|
|
463
468
|
# Responsible for decoding received headers and maintaining compression
|
464
469
|
# context of the opposing peer. Decompressor must be initialized with
|
465
470
|
# appropriate starting context based on local role: client or server.
|
466
|
-
#
|
467
|
-
# @example
|
468
|
-
# server_role = Decompressor.new(:request)
|
469
|
-
# client_role = Decompressor.new(:response)
|
470
471
|
class Decompressor
|
471
472
|
# @param options [Hash] decoding options. Only :table_size is effective.
|
473
|
+
# @see EncodingContext#initialize
|
472
474
|
def initialize(**options)
|
473
475
|
@cc = EncodingContext.new(**options)
|
474
476
|
end
|
@@ -485,16 +487,18 @@ module HTTP2
|
|
485
487
|
# @param n [Integer] number of available bits
|
486
488
|
# @return [Integer]
|
487
489
|
def integer(buf, n)
|
488
|
-
limit = 2**n - 1
|
489
|
-
i =
|
490
|
+
limit = (2**n) - 1
|
491
|
+
i = n.zero? ? 0 : (buf.getbyte & limit)
|
490
492
|
|
491
493
|
m = 0
|
492
|
-
|
493
|
-
|
494
|
-
|
494
|
+
if i == limit
|
495
|
+
while (byte = buf.getbyte)
|
496
|
+
i += ((byte & 127) << m)
|
497
|
+
m += 7
|
495
498
|
|
496
|
-
|
497
|
-
|
499
|
+
break if (byte & 128).zero?
|
500
|
+
end
|
501
|
+
end
|
498
502
|
|
499
503
|
i
|
500
504
|
end
|
@@ -508,7 +512,8 @@ module HTTP2
|
|
508
512
|
huffman = (buf.readbyte(0) & 0x80) == 0x80
|
509
513
|
len = integer(buf, 7)
|
510
514
|
str = buf.read(len)
|
511
|
-
|
515
|
+
raise CompressionError, 'string too short' unless str.bytesize == len
|
516
|
+
|
512
517
|
str = Huffman.new.decode(Buffer.new(str)) if huffman
|
513
518
|
str.force_encoding(Encoding::UTF_8)
|
514
519
|
end
|
@@ -526,13 +531,14 @@ module HTTP2
|
|
526
531
|
mask == desc[:pattern]
|
527
532
|
end
|
528
533
|
|
529
|
-
|
534
|
+
raise CompressionError unless header[:type]
|
530
535
|
|
531
536
|
header[:name] = integer(buf, type[:prefix])
|
532
537
|
|
533
538
|
case header[:type]
|
534
539
|
when :indexed
|
535
|
-
|
540
|
+
raise CompressionError if (header[:name]).zero?
|
541
|
+
|
536
542
|
header[:name] -= 1
|
537
543
|
when :changetablesize
|
538
544
|
header[:value] = header[:name]
|
@@ -558,10 +564,12 @@ module HTTP2
|
|
558
564
|
until buf.empty?
|
559
565
|
next_header = @cc.process(header(buf))
|
560
566
|
next if next_header.nil?
|
567
|
+
|
561
568
|
is_pseudo_header = next_header.first.start_with? ':'
|
562
569
|
if !decoding_pseudo_headers && is_pseudo_header
|
563
|
-
|
570
|
+
raise ProtocolError, 'one or more pseudo headers encountered after regular headers'
|
564
571
|
end
|
572
|
+
|
565
573
|
decoding_pseudo_headers = is_pseudo_header
|
566
574
|
list << next_header
|
567
575
|
end
|
data/lib/http/2/connection.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# Default connection and stream flow control window (64KB).
|
3
5
|
DEFAULT_FLOW_WINDOW = 65_535
|
@@ -10,28 +12,28 @@ module HTTP2
|
|
10
12
|
|
11
13
|
# Default values for SETTINGS frame, as defined by the spec.
|
12
14
|
SPEC_DEFAULT_CONNECTION_SETTINGS = {
|
13
|
-
settings_header_table_size:
|
14
|
-
settings_enable_push:
|
15
|
-
settings_max_concurrent_streams:
|
16
|
-
settings_initial_window_size:
|
17
|
-
settings_max_frame_size:
|
18
|
-
settings_max_header_list_size:
|
15
|
+
settings_header_table_size: 4096,
|
16
|
+
settings_enable_push: 1, # enabled for servers
|
17
|
+
settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
|
18
|
+
settings_initial_window_size: 65_535,
|
19
|
+
settings_max_frame_size: 16_384,
|
20
|
+
settings_max_header_list_size: (2**31) - 1 # unlimited
|
19
21
|
}.freeze
|
20
22
|
|
21
23
|
DEFAULT_CONNECTION_SETTINGS = {
|
22
|
-
settings_header_table_size:
|
23
|
-
settings_enable_push:
|
24
|
-
settings_max_concurrent_streams:
|
25
|
-
settings_initial_window_size:
|
26
|
-
settings_max_frame_size:
|
27
|
-
settings_max_header_list_size:
|
24
|
+
settings_header_table_size: 4096,
|
25
|
+
settings_enable_push: 1, # enabled for servers
|
26
|
+
settings_max_concurrent_streams: 100,
|
27
|
+
settings_initial_window_size: 65_535,
|
28
|
+
settings_max_frame_size: 16_384,
|
29
|
+
settings_max_header_list_size: (2**31) - 1 # unlimited
|
28
30
|
}.freeze
|
29
31
|
|
30
32
|
# Default stream priority (lower values are higher priority).
|
31
|
-
DEFAULT_WEIGHT
|
33
|
+
DEFAULT_WEIGHT = 16
|
32
34
|
|
33
35
|
# Default connection "fast-fail" preamble string as defined by the spec.
|
34
|
-
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
36
|
+
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
35
37
|
|
36
38
|
# Time to hold recently closed streams until purge (seconds)
|
37
39
|
RECENTLY_CLOSED_STREAMS_TTL = 15
|
@@ -43,7 +45,7 @@ module HTTP2
|
|
43
45
|
# Note that this class should not be used directly. Instead, you want to
|
44
46
|
# use either Client or Server class to drive the HTTP 2.0 exchange.
|
45
47
|
#
|
46
|
-
# rubocop:disable ClassLength
|
48
|
+
# rubocop:disable Metrics/ClassLength
|
47
49
|
class Connection
|
48
50
|
include FlowBuffer
|
49
51
|
include Emitter
|
@@ -55,12 +57,11 @@ module HTTP2
|
|
55
57
|
# Size of current connection flow control window (by default, set to
|
56
58
|
# infinity, but is automatically updated on receipt of peer settings).
|
57
59
|
attr_reader :local_window
|
58
|
-
attr_reader :remote_window
|
60
|
+
attr_reader :remote_window, :remote_settings
|
59
61
|
alias window local_window
|
60
62
|
|
61
63
|
# Current settings value for local and peer
|
62
64
|
attr_reader :local_settings
|
63
|
-
attr_reader :remote_settings
|
64
65
|
|
65
66
|
# Pending settings value
|
66
67
|
# Sent but not ack'ed settings
|
@@ -110,8 +111,8 @@ module HTTP2
|
|
110
111
|
# @param window [Integer]
|
111
112
|
# @param parent [Stream]
|
112
113
|
def new_stream(**args)
|
113
|
-
|
114
|
-
|
114
|
+
raise ConnectionClosed if @state == :closed
|
115
|
+
raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
|
115
116
|
|
116
117
|
stream = activate_stream(id: @stream_id, **args)
|
117
118
|
@stream_id += 2
|
@@ -140,10 +141,10 @@ module HTTP2
|
|
140
141
|
# @param payload [String]
|
141
142
|
def goaway(error = :no_error, payload = nil)
|
142
143
|
last_stream = if (max = @streams.max)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
max.first
|
145
|
+
else
|
146
|
+
0
|
147
|
+
end
|
147
148
|
|
148
149
|
send(type: :goaway, last_stream: last_stream,
|
149
150
|
error: error, payload: payload)
|
@@ -188,18 +189,17 @@ module HTTP2
|
|
188
189
|
# SETTINGS frame. Server connection header is SETTINGS frame only.
|
189
190
|
if @state == :waiting_magic
|
190
191
|
if @recv_buffer.size < 24
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
end
|
192
|
+
raise HandshakeError unless CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
|
193
|
+
|
194
|
+
return # maybe next time
|
195
|
+
|
196
196
|
elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
|
197
197
|
# MAGIC is OK. Send our settings
|
198
198
|
@state = :waiting_connection_preface
|
199
199
|
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
200
200
|
settings(payload)
|
201
201
|
else
|
202
|
-
|
202
|
+
raise HandshakeError
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
@@ -209,9 +209,7 @@ module HTTP2
|
|
209
209
|
# Header blocks MUST be transmitted as a contiguous sequence of frames
|
210
210
|
# with no interleaved frames of any other type, or from any other stream.
|
211
211
|
unless @continuation.empty?
|
212
|
-
unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
|
213
|
-
connection_error
|
214
|
-
end
|
212
|
+
connection_error unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
|
215
213
|
|
216
214
|
@continuation << frame
|
217
215
|
return unless frame[:flags].include? :end_headers
|
@@ -238,7 +236,7 @@ module HTTP2
|
|
238
236
|
when :headers
|
239
237
|
# When server receives even-numbered stream identifier,
|
240
238
|
# the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
|
241
|
-
connection_error if frame[:stream].even? &&
|
239
|
+
connection_error if frame[:stream].even? && is_a?(Server)
|
242
240
|
|
243
241
|
# The last frame in a sequence of HEADERS/CONTINUATION
|
244
242
|
# frames MUST have the END_HEADERS flag set.
|
@@ -258,10 +256,10 @@ module HTTP2
|
|
258
256
|
stream = @streams[frame[:stream]]
|
259
257
|
if stream.nil?
|
260
258
|
stream = activate_stream(
|
261
|
-
id:
|
262
|
-
weight:
|
259
|
+
id: frame[:stream],
|
260
|
+
weight: frame[:weight] || DEFAULT_WEIGHT,
|
263
261
|
dependency: frame[:dependency] || 0,
|
264
|
-
exclusive:
|
262
|
+
exclusive: frame[:exclusive] || false
|
265
263
|
)
|
266
264
|
emit(:stream, stream)
|
267
265
|
end
|
@@ -333,10 +331,10 @@ module HTTP2
|
|
333
331
|
# unused or closed parent stream.
|
334
332
|
when :priority
|
335
333
|
stream = activate_stream(
|
336
|
-
id:
|
337
|
-
weight:
|
334
|
+
id: frame[:stream],
|
335
|
+
weight: frame[:weight] || DEFAULT_WEIGHT,
|
338
336
|
dependency: frame[:dependency] || 0,
|
339
|
-
exclusive:
|
337
|
+
exclusive: frame[:exclusive] || false
|
340
338
|
)
|
341
339
|
|
342
340
|
emit(:stream, stream)
|
@@ -358,9 +356,9 @@ module HTTP2
|
|
358
356
|
end
|
359
357
|
end
|
360
358
|
end
|
361
|
-
|
362
359
|
rescue StandardError => e
|
363
360
|
raise if e.is_a?(Error::Error)
|
361
|
+
|
364
362
|
connection_error(e: e)
|
365
363
|
end
|
366
364
|
|
@@ -381,17 +379,15 @@ module HTTP2
|
|
381
379
|
if frame[:type] == :data
|
382
380
|
send_data(frame, true)
|
383
381
|
|
384
|
-
|
382
|
+
elsif frame[:type] == :rst_stream && frame[:error] == :protocol_error
|
385
383
|
# An endpoint can end a connection at any time. In particular, an
|
386
384
|
# endpoint MAY choose to treat a stream error as a connection error.
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
frames.each { |f| emit(:frame, f) }
|
394
|
-
end
|
385
|
+
goaway(frame[:error])
|
386
|
+
else
|
387
|
+
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
388
|
+
# RST_STREAM that are not protocol errors
|
389
|
+
frames = encode(frame)
|
390
|
+
frames.each { |f| emit(:frame, f) }
|
395
391
|
end
|
396
392
|
end
|
397
393
|
|
@@ -401,10 +397,10 @@ module HTTP2
|
|
401
397
|
# @return [Array of Buffer] encoded frame
|
402
398
|
def encode(frame)
|
403
399
|
frames = if frame[:type] == :headers || frame[:type] == :push_promise
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
400
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
401
|
+
else
|
402
|
+
[frame] # otherwise one frame
|
403
|
+
end
|
408
404
|
|
409
405
|
frames.map { |f| @framer.generate(f) }
|
410
406
|
end
|
@@ -460,9 +456,7 @@ module HTTP2
|
|
460
456
|
# 4. The ALTSVC HTTP/2 Frame
|
461
457
|
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
462
458
|
# information is invalid and MUST be ignored.
|
463
|
-
if frame[:origin] && !frame[:origin].empty?
|
464
|
-
emit(frame[:type], frame)
|
465
|
-
end
|
459
|
+
emit(frame[:type], frame) if frame[:origin] && !frame[:origin].empty?
|
466
460
|
when :blocked
|
467
461
|
emit(frame[:type], frame)
|
468
462
|
else
|
@@ -495,9 +489,7 @@ module HTTP2
|
|
495
489
|
when :client
|
496
490
|
# Any value other than 0 or 1 MUST be treated as a
|
497
491
|
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
498
|
-
unless v.zero? || v == 1
|
499
|
-
return ProtocolError.new("invalid #{key} value")
|
500
|
-
end
|
492
|
+
return ProtocolError.new("invalid #{key} value") unless v.zero? || v == 1
|
501
493
|
end
|
502
494
|
when :settings_max_concurrent_streams
|
503
495
|
# Any value is valid
|
@@ -505,18 +497,14 @@ module HTTP2
|
|
505
497
|
# Values above the maximum flow control window size of 2^31-1 MUST
|
506
498
|
# be treated as a connection error (Section 5.4.1) of type
|
507
499
|
# FLOW_CONTROL_ERROR.
|
508
|
-
unless v <= 0x7fffffff
|
509
|
-
return FlowControlError.new("invalid #{key} value")
|
510
|
-
end
|
500
|
+
return FlowControlError.new("invalid #{key} value") unless v <= 0x7fffffff
|
511
501
|
when :settings_max_frame_size
|
512
502
|
# The initial value is 2^14 (16,384) octets. The value advertised
|
513
503
|
# by an endpoint MUST be between this initial value and the maximum
|
514
504
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
515
505
|
# Values outside this range MUST be treated as a connection error
|
516
506
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
517
|
-
unless v >= 16_384 && v <= 16_777_215
|
518
|
-
return ProtocolError.new("invalid #{key} value")
|
519
|
-
end
|
507
|
+
return ProtocolError.new("invalid #{key} value") unless v >= 16_384 && v <= 16_777_215
|
520
508
|
when :settings_max_header_list_size
|
521
509
|
# Any value is valid
|
522
510
|
# else # ignore unknown settings
|
@@ -536,12 +524,12 @@ module HTTP2
|
|
536
524
|
# local: previously sent and pended our settings should be effective
|
537
525
|
# remote: just received peer settings should immediately be effective
|
538
526
|
settings, side = if frame[:flags].include?(:ack)
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
527
|
+
# Process pending settings we have sent.
|
528
|
+
[@pending_settings.shift, :local]
|
529
|
+
else
|
530
|
+
connection_error if validate_settings(@remote_role, frame[:payload])
|
531
|
+
[frame[:payload], :remote]
|
532
|
+
end
|
545
533
|
|
546
534
|
settings.each do |key, v|
|
547
535
|
case side
|
@@ -565,14 +553,14 @@ module HTTP2
|
|
565
553
|
case side
|
566
554
|
when :local
|
567
555
|
@local_window = @local_window - @local_window_limit + v
|
568
|
-
@streams.
|
556
|
+
@streams.each_value do |stream|
|
569
557
|
stream.emit(:local_window, stream.local_window - @local_window_limit + v)
|
570
558
|
end
|
571
559
|
|
572
560
|
@local_window_limit = v
|
573
561
|
when :remote
|
574
562
|
@remote_window = @remote_window - @remote_window_limit + v
|
575
|
-
@streams.
|
563
|
+
@streams.each_value do |stream|
|
576
564
|
# Event name is :window, not :remote_window
|
577
565
|
stream.emit(:window, stream.remote_window - @remote_window_limit + v)
|
578
566
|
end
|
@@ -621,10 +609,7 @@ module HTTP2
|
|
621
609
|
#
|
622
610
|
# @param frame [Hash]
|
623
611
|
def decode_headers(frame)
|
624
|
-
if frame[:payload].is_a? Buffer
|
625
|
-
frame[:payload] = @decompressor.decode(frame[:payload])
|
626
|
-
end
|
627
|
-
|
612
|
+
frame[:payload] = @decompressor.decode(frame[:payload]) if frame[:payload].is_a? Buffer
|
628
613
|
rescue CompressionError => e
|
629
614
|
connection_error(:compression_error, e: e)
|
630
615
|
rescue ProtocolError => e
|
@@ -643,7 +628,7 @@ module HTTP2
|
|
643
628
|
|
644
629
|
frames = []
|
645
630
|
|
646
|
-
while payload.bytesize
|
631
|
+
while payload.bytesize.positive?
|
647
632
|
cont = frame.dup
|
648
633
|
cont[:type] = :continuation
|
649
634
|
cont[:flags] = []
|
@@ -659,7 +644,6 @@ module HTTP2
|
|
659
644
|
end
|
660
645
|
|
661
646
|
frames
|
662
|
-
|
663
647
|
rescue StandardError => e
|
664
648
|
connection_error(:compression_error, e: e)
|
665
649
|
nil
|
@@ -675,7 +659,7 @@ module HTTP2
|
|
675
659
|
def activate_stream(id: nil, **args)
|
676
660
|
connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
|
677
661
|
|
678
|
-
stream = Stream.new(
|
662
|
+
stream = Stream.new(connection: self, id: id, **args)
|
679
663
|
|
680
664
|
# Streams that are in the "open" state, or either of the "half closed"
|
681
665
|
# states count toward the maximum number of streams that an endpoint is
|
@@ -692,7 +676,7 @@ module HTTP2
|
|
692
676
|
cleanup_recently_closed
|
693
677
|
end
|
694
678
|
|
695
|
-
stream.on(:promise, &method(:promise)) if
|
679
|
+
stream.on(:promise, &method(:promise)) if is_a? Server
|
696
680
|
stream.on(:frame, &method(:send))
|
697
681
|
|
698
682
|
@streams[id] = stream
|
@@ -705,6 +689,7 @@ module HTTP2
|
|
705
689
|
@streams_recently_closed.each do |stream_id, ts|
|
706
690
|
# Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
|
707
691
|
break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
|
692
|
+
|
708
693
|
to_delete << stream_id
|
709
694
|
end
|
710
695
|
|
@@ -728,11 +713,12 @@ module HTTP2
|
|
728
713
|
def connection_error(error = :protocol_error, msg: nil, e: nil)
|
729
714
|
goaway(error) unless @state == :closed || @state == :new
|
730
715
|
|
731
|
-
@state
|
716
|
+
@state = :closed
|
717
|
+
@error = error
|
732
718
|
klass = error.to_s.split('_').map(&:capitalize).join
|
733
|
-
msg ||= e
|
734
|
-
backtrace =
|
735
|
-
|
719
|
+
msg ||= e&.message
|
720
|
+
backtrace = e&.backtrace || []
|
721
|
+
raise Error.const_get(klass), msg, backtrace
|
736
722
|
end
|
737
723
|
alias error connection_error
|
738
724
|
|
@@ -740,5 +726,5 @@ module HTTP2
|
|
740
726
|
yield
|
741
727
|
end
|
742
728
|
end
|
743
|
-
# rubocop:enable ClassLength
|
729
|
+
# rubocop:enable Metrics/ClassLength
|
744
730
|
end
|