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
data/lib/http/2/emitter.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# Basic event emitter implementation with support for persistent and
|
3
5
|
# one-time event callbacks.
|
@@ -7,18 +9,18 @@ module HTTP2
|
|
7
9
|
#
|
8
10
|
# @param event [Symbol]
|
9
11
|
# @param block [Proc] callback function
|
10
|
-
def
|
11
|
-
|
12
|
+
def on(event, &block)
|
13
|
+
raise ArgumentError, "must provide callback" unless block
|
14
|
+
|
12
15
|
listeners(event.to_sym).push block
|
13
16
|
end
|
14
|
-
alias on add_listener
|
15
17
|
|
16
18
|
# Subscribe to next event (at most once) for specified type.
|
17
19
|
#
|
18
20
|
# @param event [Symbol]
|
19
21
|
# @param block [Proc] callback function
|
20
22
|
def once(event, &block)
|
21
|
-
|
23
|
+
on(event) do |*args, &callback|
|
22
24
|
block.call(*args, &callback)
|
23
25
|
:delete
|
24
26
|
end
|
@@ -31,7 +33,7 @@ module HTTP2
|
|
31
33
|
# @param block [Proc] callback function
|
32
34
|
def emit(event, *args, &block)
|
33
35
|
listeners(event).delete_if do |cb|
|
34
|
-
cb.call(*args, &block)
|
36
|
+
:delete == cb.call(*args, &block) # rubocop:disable Style/YodaCondition
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
data/lib/http/2/error.rb
CHANGED
@@ -1,7 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# Stream, connection, and compressor exceptions.
|
3
5
|
module Error
|
4
|
-
|
6
|
+
@types = {}
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :types
|
10
|
+
end
|
11
|
+
|
12
|
+
class Error < StandardError
|
13
|
+
def self.inherited(klass)
|
14
|
+
super
|
15
|
+
|
16
|
+
type = klass.name or return
|
17
|
+
|
18
|
+
type = type.split("::").last or return
|
19
|
+
|
20
|
+
type = type.gsub(/([^\^])([A-Z])/, '\1_\2').downcase.to_sym
|
21
|
+
HTTP2::Error.types[type] = klass
|
22
|
+
end
|
23
|
+
end
|
5
24
|
|
6
25
|
# Raised if connection header is missing or invalid indicating that
|
7
26
|
# this is an invalid HTTP 2.0 request - no frames are emitted and the
|
@@ -40,5 +59,9 @@ module HTTP2
|
|
40
59
|
|
41
60
|
# Raised if stream limit has been reached and new stream cannot be opened.
|
42
61
|
class StreamLimitExceeded < Error; end
|
62
|
+
|
63
|
+
class FrameSizeError < Error; end
|
64
|
+
|
65
|
+
@types.freeze
|
43
66
|
end
|
44
67
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2
|
4
|
+
module StringExtensions
|
5
|
+
refine String do
|
6
|
+
def read(n)
|
7
|
+
return "".b if n == 0
|
8
|
+
|
9
|
+
chunk = byteslice(0..n - 1)
|
10
|
+
remaining = byteslice(n..-1)
|
11
|
+
remaining ? replace(remaining) : clear
|
12
|
+
chunk
|
13
|
+
end
|
14
|
+
|
15
|
+
def read_uint32
|
16
|
+
read(4).unpack1("N")
|
17
|
+
end
|
18
|
+
|
19
|
+
def shift_byte
|
20
|
+
read(1).ord
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# this mixin handles backwards-compatibility for the new packing options
|
26
|
+
# shipping with ruby 3.3 (see https://docs.ruby-lang.org/en/3.3/packed_data_rdoc.html)
|
27
|
+
module PackingExtensions
|
28
|
+
if RUBY_VERSION < "3.3.0"
|
29
|
+
def pack(array_to_pack, template, buffer:, offset: -1)
|
30
|
+
packed_str = array_to_pack.pack(template)
|
31
|
+
case offset
|
32
|
+
when -1
|
33
|
+
buffer << packed_str
|
34
|
+
when 0
|
35
|
+
buffer.prepend(packed_str)
|
36
|
+
else
|
37
|
+
buffer.insert(offset, packed_str)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def pack(array_to_pack, template, buffer:, offset: -1)
|
42
|
+
case offset
|
43
|
+
when -1
|
44
|
+
array_to_pack.pack(template, buffer: buffer)
|
45
|
+
when 0
|
46
|
+
buffer.prepend(array_to_pack.pack(template))
|
47
|
+
else
|
48
|
+
buffer.insert(offset, array_to_pack.pack(template))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/http/2/flow_buffer.rb
CHANGED
@@ -1,18 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# Implementation of stream and connection DATA flow control: frames may
|
3
5
|
# be split and / or may be buffered based on current flow control window.
|
4
6
|
#
|
5
7
|
module FlowBuffer
|
8
|
+
include Error
|
9
|
+
|
10
|
+
MAX_WINDOW_SIZE = (2 << 30) - 1
|
11
|
+
|
6
12
|
# Amount of buffered data. Only DATA payloads are subject to flow stream
|
7
13
|
# and connection flow control.
|
8
14
|
#
|
9
15
|
# @return [Integer]
|
10
16
|
def buffered_amount
|
11
|
-
|
17
|
+
send_buffer.bytesize
|
18
|
+
end
|
19
|
+
|
20
|
+
def flush
|
21
|
+
send_data
|
12
22
|
end
|
13
23
|
|
14
24
|
private
|
15
25
|
|
26
|
+
def send_buffer
|
27
|
+
@send_buffer ||= FrameBuffer.new
|
28
|
+
end
|
29
|
+
|
16
30
|
def update_local_window(frame)
|
17
31
|
frame_size = frame[:payload].bytesize
|
18
32
|
frame_size += frame[:padding] || 0
|
@@ -41,6 +55,7 @@ module HTTP2
|
|
41
55
|
# until the receiver window is exhausted, after which he'll be
|
42
56
|
# waiting for the WINDOW_UPDATE frame.
|
43
57
|
return unless @local_window <= (window_max_size / 2)
|
58
|
+
|
44
59
|
window_update(window_max_size - @local_window)
|
45
60
|
end
|
46
61
|
|
@@ -52,49 +67,92 @@ module HTTP2
|
|
52
67
|
# Buffered DATA frames are emitted in FIFO order.
|
53
68
|
#
|
54
69
|
# @param frame [Hash]
|
55
|
-
# @param encode [Boolean] set to true by
|
70
|
+
# @param encode [Boolean] set to true by connection
|
56
71
|
def send_data(frame = nil, encode = false)
|
57
|
-
|
72
|
+
send_buffer << frame unless frame.nil?
|
58
73
|
|
59
|
-
|
60
|
-
# is, an empty DATA frame) MAY be sent if there is no available space
|
61
|
-
# in either flow control window.
|
62
|
-
while @remote_window > 0 && !@send_buffer.empty?
|
63
|
-
frame = @send_buffer.shift
|
74
|
+
while (frame = send_buffer.retrieve(@remote_window))
|
64
75
|
|
65
|
-
|
66
|
-
|
67
|
-
if frame_size > @remote_window
|
68
|
-
payload = frame.delete(:payload)
|
69
|
-
chunk = frame.dup
|
70
|
-
|
71
|
-
# Split frame so that it fits in the window
|
72
|
-
# TODO: consider padding!
|
73
|
-
frame[:payload] = payload.slice!(0, @remote_window)
|
74
|
-
chunk[:length] = payload.bytesize
|
75
|
-
chunk[:payload] = payload
|
76
|
-
|
77
|
-
# if no longer last frame in sequence...
|
78
|
-
frame[:flags] -= [:end_stream] if frame[:flags].include? :end_stream
|
79
|
-
|
80
|
-
@send_buffer.unshift chunk
|
81
|
-
sent = @remote_window
|
82
|
-
else
|
83
|
-
sent = frame_size
|
84
|
-
end
|
76
|
+
# puts "#{self.class} -> #{@remote_window}"
|
77
|
+
sent = frame[:payload].bytesize
|
85
78
|
|
86
79
|
manage_state(frame) do
|
87
|
-
|
88
|
-
|
80
|
+
if encode
|
81
|
+
encode(frame).each { |f| emit(:frame, f) }
|
82
|
+
else
|
83
|
+
emit(:frame, frame)
|
84
|
+
end
|
89
85
|
@remote_window -= sent
|
90
86
|
end
|
91
87
|
end
|
92
88
|
end
|
93
89
|
|
94
|
-
def process_window_update(frame)
|
90
|
+
def process_window_update(frame:, encode: false)
|
95
91
|
return if frame[:ignore]
|
96
|
-
|
97
|
-
|
92
|
+
|
93
|
+
if frame[:increment]
|
94
|
+
raise ProtocolError, "increment MUST be higher than zero" if frame[:increment].zero?
|
95
|
+
|
96
|
+
@remote_window += frame[:increment]
|
97
|
+
error(:flow_control_error, msg: "window size too large") if @remote_window > MAX_WINDOW_SIZE
|
98
|
+
end
|
99
|
+
send_data(nil, encode)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class FrameBuffer
|
104
|
+
attr_reader :bytesize
|
105
|
+
|
106
|
+
def initialize
|
107
|
+
@buffer = []
|
108
|
+
@bytesize = 0
|
109
|
+
end
|
110
|
+
|
111
|
+
def <<(frame)
|
112
|
+
@buffer << frame
|
113
|
+
@bytesize += frame[:payload].bytesize
|
114
|
+
end
|
115
|
+
|
116
|
+
def empty?
|
117
|
+
@bytesize.zero?
|
118
|
+
end
|
119
|
+
|
120
|
+
def retrieve(window_size)
|
121
|
+
frame = @buffer.first or return
|
122
|
+
|
123
|
+
frame_size = frame[:payload].bytesize
|
124
|
+
end_stream = frame[:flags].include? :end_stream
|
125
|
+
|
126
|
+
# Frames with zero length with the END_STREAM flag set (that
|
127
|
+
# is, an empty DATA frame) MAY be sent if there is no available space
|
128
|
+
# in either flow control window.
|
129
|
+
return if window_size <= 0 && !(frame_size == 0 && end_stream)
|
130
|
+
|
131
|
+
@buffer.shift
|
132
|
+
|
133
|
+
if frame_size > window_size
|
134
|
+
payload = frame[:payload]
|
135
|
+
chunk = frame.dup
|
136
|
+
|
137
|
+
# Split frame so that it fits in the window
|
138
|
+
# TODO: consider padding!
|
139
|
+
frame_bytes = payload.byteslice(0, window_size)
|
140
|
+
payload = payload.byteslice(window_size..-1)
|
141
|
+
|
142
|
+
frame[:payload] = frame_bytes
|
143
|
+
frame[:length] = frame_bytes.bytesize
|
144
|
+
chunk[:payload] = payload
|
145
|
+
chunk[:length] = payload.bytesize
|
146
|
+
|
147
|
+
# if no longer last frame in sequence...
|
148
|
+
frame[:flags] -= [:end_stream] if end_stream
|
149
|
+
|
150
|
+
@buffer.unshift(chunk)
|
151
|
+
@bytesize -= window_size
|
152
|
+
else
|
153
|
+
@bytesize -= frame_size
|
154
|
+
end
|
155
|
+
frame
|
98
156
|
end
|
99
157
|
end
|
100
158
|
end
|