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
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
|