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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -9
  3. data/lib/http/2/base64.rb +45 -0
  4. data/lib/http/2/client.rb +19 -6
  5. data/lib/http/2/connection.rb +235 -163
  6. data/lib/http/2/emitter.rb +7 -5
  7. data/lib/http/2/error.rb +24 -1
  8. data/lib/http/2/extensions.rb +53 -0
  9. data/lib/http/2/flow_buffer.rb +91 -33
  10. data/lib/http/2/framer.rb +184 -157
  11. data/lib/http/2/header/compressor.rb +157 -0
  12. data/lib/http/2/header/decompressor.rb +144 -0
  13. data/lib/http/2/header/encoding_context.rb +337 -0
  14. data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
  15. data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
  16. data/lib/http/2/header.rb +35 -0
  17. data/lib/http/2/server.rb +47 -20
  18. data/lib/http/2/stream.rb +130 -61
  19. data/lib/http/2/version.rb +3 -1
  20. data/lib/http/2.rb +14 -13
  21. data/sig/client.rbs +9 -0
  22. data/sig/connection.rbs +93 -0
  23. data/sig/emitter.rbs +13 -0
  24. data/sig/error.rbs +35 -0
  25. data/sig/extensions.rbs +5 -0
  26. data/sig/flow_buffer.rbs +21 -0
  27. data/sig/frame_buffer.rbs +13 -0
  28. data/sig/framer.rbs +54 -0
  29. data/sig/header/compressor.rbs +27 -0
  30. data/sig/header/decompressor.rbs +22 -0
  31. data/sig/header/encoding_context.rbs +34 -0
  32. data/sig/header/huffman.rbs +9 -0
  33. data/sig/header.rbs +27 -0
  34. data/sig/next.rbs +101 -0
  35. data/sig/server.rbs +12 -0
  36. data/sig/stream.rbs +91 -0
  37. metadata +38 -79
  38. data/.autotest +0 -20
  39. data/.coveralls.yml +0 -1
  40. data/.gitignore +0 -20
  41. data/.gitmodules +0 -3
  42. data/.rspec +0 -5
  43. data/.rubocop.yml +0 -93
  44. data/.rubocop_todo.yml +0 -131
  45. data/.travis.yml +0 -17
  46. data/Gemfile +0 -16
  47. data/Guardfile +0 -18
  48. data/Guardfile.h2spec +0 -12
  49. data/LICENSE +0 -21
  50. data/Rakefile +0 -49
  51. data/example/Gemfile +0 -3
  52. data/example/README.md +0 -44
  53. data/example/client.rb +0 -122
  54. data/example/helper.rb +0 -19
  55. data/example/keys/server.crt +0 -20
  56. data/example/keys/server.key +0 -27
  57. data/example/server.rb +0 -139
  58. data/example/upgrade_client.rb +0 -153
  59. data/example/upgrade_server.rb +0 -203
  60. data/http-2.gemspec +0 -22
  61. data/lib/http/2/buffer.rb +0 -76
  62. data/lib/http/2/compressor.rb +0 -572
  63. data/lib/tasks/generate_huffman_table.rb +0 -166
  64. data/spec/buffer_spec.rb +0 -28
  65. data/spec/client_spec.rb +0 -188
  66. data/spec/compressor_spec.rb +0 -666
  67. data/spec/connection_spec.rb +0 -681
  68. data/spec/emitter_spec.rb +0 -54
  69. data/spec/framer_spec.rb +0 -487
  70. data/spec/h2spec/h2spec.darwin +0 -0
  71. data/spec/h2spec/output/non_secure.txt +0 -317
  72. data/spec/helper.rb +0 -147
  73. data/spec/hpack_test_spec.rb +0 -84
  74. data/spec/huffman_spec.rb +0 -68
  75. data/spec/server_spec.rb +0 -52
  76. data/spec/stream_spec.rb +0 -878
  77. data/spec/support/deep_dup.rb +0 -55
  78. data/spec/support/duplicable.rb +0 -98
@@ -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 add_listener(event, &block)
11
- fail ArgumentError, 'must provide callback' unless block_given?
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
- add_listener(event) do |*args, &callback|
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) == :delete
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
- class Error < StandardError; end
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
@@ -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
- @send_buffer.map { |f| f[:length] }.reduce(:+) || 0
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 co
70
+ # @param encode [Boolean] set to true by connection
56
71
  def send_data(frame = nil, encode = false)
57
- @send_buffer.push frame unless frame.nil?
72
+ send_buffer << frame unless frame.nil?
58
73
 
59
- # FIXME: Frames with zero length with the END_STREAM flag set (that
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
- sent, frame_size = 0, frame[:payload].bytesize
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
- frames = encode ? encode(frame) : [frame]
88
- frames.each { |f| emit(:frame, f) }
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
- @remote_window += frame[:increment]
97
- send_data
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