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