http-2-next 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba5613efe08f91c20db5d9c32fbbac18a2d6f214ba42037c341dd9b40cbffe23
4
- data.tar.gz: c7e0f3a0256a2cf7d106c1be72417c863e37f4c203bc7df6910ff91d135479e6
3
+ metadata.gz: c4ce4e3302e32d2c860cf38d25f82186a233ae9bb446bde932d185ca398d4eec
4
+ data.tar.gz: c2428f11638405fa7c92390745f4c21022627500fe54d564a46f06e83967d126
5
5
  SHA512:
6
- metadata.gz: 645582d2208c23b84120256893828f8fc5f3c7d058848979fd62188a9fd31746aafade3759334d06ae8d53b2ee6ab3bff23d4eb5288112da31ccf6dee8a187cf
7
- data.tar.gz: 3804926e00e854b61a25dbf0e515ae5fcfb82532403909f6d3e4e035f7ebeef162c14bc00d64babd0013cdb22f2a7164f02dc708d1575392fc8b3445ddd953f6
6
+ metadata.gz: 5a10531d34c3d37989a6de029b4bfdca1b9742b7614a7785179bea4d8794568b58bd88641c259721520f803b1f6577c2e41ddb49bff75564edfbdf9d422124fc
7
+ data.tar.gz: 8d9d7e74f52d079a7f9021bcd7679bd7544b1e30d17cdd5f42de24326812c46f4c3f8208f3132ba0e9d0df46b6c947238781a48abbf6d1cacc95da69b3227293
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/http-2-next.svg)](http://rubygems.org/gems/http-2-next)
4
4
  [![Build status](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/http-2-next/commits/master)
5
- [![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
5
+ [![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
6
6
 
7
7
  **Attention!** This is a fork of the [http-2](https://github.com/igrigorik/http-2) gem.
8
8
 
@@ -6,9 +6,7 @@ require "http/2/next/error"
6
6
  require "http/2/next/emitter"
7
7
  require "http/2/next/buffer"
8
8
  require "http/2/next/flow_buffer"
9
- require "http/2/next/huffman"
10
- require "http/2/next/huffman_statemachine"
11
- require "http/2/next/compressor"
9
+ require "http/2/next/header"
12
10
  require "http/2/next/framer"
13
11
  require "http/2/next/connection"
14
12
  require "http/2/next/client"
@@ -8,6 +8,8 @@ module HTTP2Next
8
8
  class Buffer
9
9
  extend Forwardable
10
10
 
11
+ using StringExtensions
12
+
11
13
  def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
12
14
  :size, :each_byte, :to_str, :to_s, :length, :inspect,
13
15
  :[], :[]=, :empty?, :bytesize, :include?
@@ -28,6 +30,10 @@ module HTTP2Next
28
30
  Buffer.new(@buffer.slice!(0, n))
29
31
  end
30
32
 
33
+ def unpack1(*arg)
34
+ @buffer.unpack1(*arg)
35
+ end
36
+
31
37
  # Emulate StringIO#getbyte: slice first byte from buffer.
32
38
  def getbyte
33
39
  read(1).ord
@@ -61,7 +67,7 @@ module HTTP2Next
61
67
  # Slice unsigned 32-bit integer from buffer.
62
68
  # @return [Integer]
63
69
  def read_uint32
64
- read(4).unpack(UINT32).first
70
+ read(4).unpack1(UINT32)
65
71
  end
66
72
 
67
73
  # Ensures that data that is added is binary encoded as well,
@@ -57,12 +57,11 @@ module HTTP2Next
57
57
  # Size of current connection flow control window (by default, set to
58
58
  # infinity, but is automatically updated on receipt of peer settings).
59
59
  attr_reader :local_window
60
- attr_reader :remote_window
60
+ attr_reader :remote_window, :remote_settings
61
61
  alias window local_window
62
62
 
63
63
  # Current settings value for local and peer
64
64
  attr_reader :local_settings
65
- attr_reader :remote_settings
66
65
 
67
66
  # Pending settings value
68
67
  # Sent but not ack'ed settings
@@ -105,6 +104,7 @@ module HTTP2Next
105
104
 
106
105
  @h2c_upgrade = nil
107
106
  @closed_since = nil
107
+ @received_frame = false
108
108
  end
109
109
 
110
110
  def closed?
@@ -176,7 +176,7 @@ module HTTP2Next
176
176
  # @param settings [Array or Hash]
177
177
  def settings(payload)
178
178
  payload = payload.to_a
179
- connection_error if validate_settings(@local_role, payload)
179
+ validate_settings(@local_role, payload)
180
180
  @pending_settings << payload
181
181
  send(type: :settings, stream: 0, payload: payload)
182
182
  @pending_settings << payload
@@ -213,6 +213,11 @@ module HTTP2Next
213
213
  end
214
214
 
215
215
  while (frame = @framer.parse(@recv_buffer))
216
+ if is_a?(Client) && !@received_frame
217
+ connection_error(:protocol_error, msg: "didn't receive settings") if frame[:type] != :settings
218
+ @received_frame = true
219
+ end
220
+
216
221
  # Implementations MUST discard frames
217
222
  # that have unknown or unsupported types.
218
223
  if frame[:type].nil?
@@ -521,8 +526,6 @@ module HTTP2Next
521
526
  def validate_settings(role, settings)
522
527
  settings.each do |key, v|
523
528
  case key
524
- when :settings_header_table_size
525
- # Any value is valid
526
529
  when :settings_enable_push
527
530
  case role
528
531
  when :server
@@ -530,32 +533,41 @@ module HTTP2Next
530
533
  # Clients MUST reject any attempt to change the
531
534
  # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
532
535
  # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
533
- return ProtocolError.new("invalid #{key} value") unless v.zero?
536
+ next if v.zero?
537
+
538
+ connection_error(:protocol_error, msg: "invalid #{key} value")
534
539
  when :client
535
540
  # Any value other than 0 or 1 MUST be treated as a
536
541
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
537
- return ProtocolError.new("invalid #{key} value") unless v.zero? || v == 1
542
+ next if v.zero? || v == 1
543
+
544
+ connection_error(:protocol_error, msg: "invalid #{key} value")
538
545
  end
539
- when :settings_max_concurrent_streams
540
- # Any value is valid
541
546
  when :settings_initial_window_size
542
547
  # Values above the maximum flow control window size of 2^31-1 MUST
543
548
  # be treated as a connection error (Section 5.4.1) of type
544
549
  # FLOW_CONTROL_ERROR.
545
- return FlowControlError.new("invalid #{key} value") unless v <= 0x7fffffff
550
+ next if v <= 0x7fffffff
551
+
552
+ connection_error(:flow_control_error, msg: "invalid #{key} value")
546
553
  when :settings_max_frame_size
547
554
  # The initial value is 2^14 (16,384) octets. The value advertised
548
555
  # by an endpoint MUST be between this initial value and the maximum
549
556
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
550
557
  # Values outside this range MUST be treated as a connection error
551
558
  # (Section 5.4.1) of type PROTOCOL_ERROR.
552
- return ProtocolError.new("invalid #{key} value") unless v >= 16_384 && v <= 16_777_215
553
- when :settings_max_header_list_size
559
+ next if v >= 16_384 && v <= 16_777_215
560
+
561
+ connection_error(:protocol_error, msg: "invalid #{key} value")
562
+ # when :settings_max_concurrent_streams
563
+ # Any value is valid
564
+ # when :settings_header_table_size
565
+ # Any value is valid
566
+ # when :settings_max_header_list_size
554
567
  # Any value is valid
555
568
  # else # ignore unknown settings
556
569
  end
557
570
  end
558
- nil
559
571
  end
560
572
 
561
573
  # Update connection settings based on parameters set by the peer.
@@ -572,8 +584,7 @@ module HTTP2Next
572
584
  # Process pending settings we have sent.
573
585
  [@pending_settings.shift, :local]
574
586
  else
575
- check = validate_settings(@remote_role, frame[:payload])
576
- connection_error(check) if check
587
+ validate_settings(@remote_role, frame[:payload])
577
588
  [frame[:payload], :remote]
578
589
  end
579
590
 
@@ -707,7 +718,7 @@ module HTTP2Next
707
718
  # @param priority [Integer]
708
719
  # @param window [Integer]
709
720
  # @param parent [Stream]
710
- def activate_stream(id: nil, **args)
721
+ def activate_stream(id:, **args)
711
722
  connection_error(msg: "Stream ID already exists") if @streams.key?(id)
712
723
 
713
724
  raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @local_settings[:settings_max_concurrent_streams])
@@ -9,19 +9,18 @@ module HTTP2Next
9
9
  #
10
10
  # @param event [Symbol]
11
11
  # @param block [Proc] callback function
12
- def add_listener(event, &block)
12
+ def on(event, &block)
13
13
  raise ArgumentError, "must provide callback" unless block_given?
14
14
 
15
15
  listeners(event.to_sym).push block
16
16
  end
17
- alias on add_listener
18
17
 
19
18
  # Subscribe to next event (at most once) for specified type.
20
19
  #
21
20
  # @param event [Symbol]
22
21
  # @param block [Proc] callback function
23
22
  def once(event, &block)
24
- add_listener(event) do |*args, &callback|
23
+ on(event) do |*args, &callback|
25
24
  block.call(*args, &callback)
26
25
  :delete
27
26
  end
@@ -10,6 +10,7 @@ module HTTP2Next
10
10
 
11
11
  class Error < StandardError
12
12
  def self.inherited(klass)
13
+ super
13
14
  type = klass.name.split("::").last
14
15
  type = type.gsub(/([^\^])([A-Z])/, '\1_\2').downcase.to_sym
15
16
  HTTP2Next::Error.types[type] = klass
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2Next
4
- module Extensions
4
+ module RegexpExtensions
5
5
  unless Regexp.method_defined?(:match?)
6
6
  refine Regexp do
7
7
  def match?(*args)
@@ -10,4 +10,14 @@ module HTTP2Next
10
10
  end
11
11
  end
12
12
  end
13
+
14
+ module StringExtensions
15
+ unless String.method_defined?(:unpack1)
16
+ refine String do
17
+ def unpack1(format)
18
+ unpack(format).first
19
+ end
20
+ end
21
+ end
22
+ end
13
23
  end
@@ -73,6 +73,7 @@ module HTTP2Next
73
73
 
74
74
  while (frame = send_buffer.retrieve(@remote_window))
75
75
 
76
+ # puts "#{self.class} -> #{@remote_window}"
76
77
  sent = frame[:payload].bytesize
77
78
 
78
79
  manage_state(frame) do
@@ -4,6 +4,8 @@ module HTTP2Next
4
4
  # Performs encoding, decoding, and validation of binary HTTP/2 frames.
5
5
  #
6
6
  class Framer
7
+ using StringExtensions
8
+
7
9
  include Error
8
10
 
9
11
  # Default value of max frame size (16384 bytes)
@@ -154,6 +156,7 @@ module HTTP2Next
154
156
  # Decodes common 9-byte header.
155
157
  #
156
158
  # @param buf [Buffer]
159
+ # @return [Hash] the corresponding frame
157
160
  def read_common_header(buf)
158
161
  frame = {}
159
162
  len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
@@ -221,7 +224,7 @@ module HTTP2Next
221
224
  raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
222
225
 
223
226
  frame[:payload].each do |(k, v)|
224
- if k.is_a? Integer
227
+ if k.is_a? Integer # rubocop:disable Style/GuardClause
225
228
  DEFINED_SETTINGS.value?(k) || next
226
229
  else
227
230
  k = DEFINED_SETTINGS[k]
@@ -333,10 +336,10 @@ module HTTP2Next
333
336
  #
334
337
  # @param buf [Buffer]
335
338
  def parse(buf)
336
- return nil if buf.size < 9
339
+ return if buf.size < 9
337
340
 
338
341
  frame = read_common_header(buf)
339
- return nil if buf.size < 9 + frame[:length]
342
+ return if buf.size < 9 + frame[:length]
340
343
 
341
344
  raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
342
345
 
@@ -353,7 +356,7 @@ module HTTP2Next
353
356
  if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
354
357
  padded = frame[:flags].include?(:padded)
355
358
  if padded
356
- padlen = payload.read(1).unpack(UINT8).first
359
+ padlen = payload.read(1).unpack1(UINT8)
357
360
  frame[:padding] = padlen + 1
358
361
  raise ProtocolError, "padding too long" if padlen > payload.bytesize
359
362
 
@@ -395,7 +398,7 @@ module HTTP2Next
395
398
  raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
396
399
 
397
400
  (frame[:length] / 6).times do
398
- id = payload.read(2).unpack(UINT16).first
401
+ id = payload.read(2).unpack1(UINT16)
399
402
  val = payload.read_uint32
400
403
 
401
404
  # Unsupported or unrecognized settings MUST be ignored.
@@ -437,7 +440,7 @@ module HTTP2Next
437
440
  origins = []
438
441
 
439
442
  until payload.empty?
440
- len = payload.read(2).unpack(UINT16).first
443
+ len = payload.read(2).unpack1(UINT16)
441
444
  origins << payload.read(len)
442
445
  end
443
446
 
@@ -452,17 +455,16 @@ module HTTP2Next
452
455
 
453
456
  def pack_error(e)
454
457
  unless e.is_a? Integer
455
- raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
456
-
457
458
  e = DEFINED_ERRORS[e]
459
+
460
+ raise CompressionError, "Unknown error ID for #{e}" unless e
458
461
  end
459
462
 
460
463
  [e].pack(UINT32)
461
464
  end
462
465
 
463
466
  def unpack_error(error)
464
- name, = DEFINED_ERRORS.find { |_name, v| v == error }
465
- name || error
467
+ DEFINED_ERRORS.key(error) || error
466
468
  end
467
469
  end
468
470
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP2Next
4
+ # Implementation of header compression for HTTP 2.0 (HPACK) format adapted
5
+ # to efficiently represent HTTP headers in the context of HTTP 2.0.
6
+ #
7
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
8
+ module Header
9
+ # Header representation as defined by the spec.
10
+ HEADREP = {
11
+ indexed: { prefix: 7, pattern: 0x80 },
12
+ incremental: { prefix: 6, pattern: 0x40 },
13
+ noindex: { prefix: 4, pattern: 0x00 },
14
+ neverindexed: { prefix: 4, pattern: 0x10 },
15
+ changetablesize: { prefix: 5, pattern: 0x20 }
16
+ }.each_value(&:freeze).freeze
17
+
18
+ # Predefined options set for Compressor
19
+ # http://mew.org/~kazu/material/2014-hpack.pdf
20
+ NAIVE = { index: :never, huffman: :never }.freeze
21
+ LINEAR = { index: :all, huffman: :never }.freeze
22
+ STATIC = { index: :static, huffman: :never }.freeze
23
+ SHORTER = { index: :all, huffman: :never }.freeze
24
+ NAIVEH = { index: :never, huffman: :always }.freeze
25
+ LINEARH = { index: :all, huffman: :always }.freeze
26
+ STATICH = { index: :static, huffman: :always }.freeze
27
+ SHORTERH = { index: :all, huffman: :shorter }.freeze
28
+ end
29
+ end
30
+
31
+ require "http/2/next/header/huffman"
32
+ require "http/2/next/header/huffman_statemachine"
33
+ require "http/2/next/header/encoding_context"
34
+ require "http/2/next/header/compressor"
35
+ require "http/2/next/header/decompressor"
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP2Next
4
+ module Header
5
+ # Responsible for encoding header key-value pairs using HPACK algorithm.
6
+ class Compressor
7
+ # @param options [Hash] encoding options
8
+ def initialize(options = {})
9
+ @cc = EncodingContext.new(options)
10
+ end
11
+
12
+ # Set dynamic table size in EncodingContext
13
+ # @param size [Integer] new dynamic table size
14
+ def table_size=(size)
15
+ @cc.table_size = size
16
+ end
17
+
18
+ # Encodes provided value via integer representation.
19
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1
20
+ #
21
+ # If I < 2^N - 1, encode I on N bits
22
+ # Else
23
+ # encode 2^N - 1 on N bits
24
+ # I = I - (2^N - 1)
25
+ # While I >= 128
26
+ # Encode (I % 128 + 128) on 8 bits
27
+ # I = I / 128
28
+ # encode (I) on 8 bits
29
+ #
30
+ # @param i [Integer] value to encode
31
+ # @param n [Integer] number of available bits
32
+ # @return [String] binary string
33
+ def integer(i, n)
34
+ limit = 2**n - 1
35
+ return [i].pack("C") if i < limit
36
+
37
+ bytes = []
38
+ bytes.push limit unless n.zero?
39
+
40
+ i -= limit
41
+ while i >= 128
42
+ bytes.push((i % 128) + 128)
43
+ i /= 128
44
+ end
45
+
46
+ bytes.push i
47
+ bytes.pack("C*")
48
+ end
49
+
50
+ # Encodes provided value via string literal representation.
51
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2
52
+ #
53
+ # * The string length, defined as the number of bytes needed to store
54
+ # its UTF-8 representation, is represented as an integer with a seven
55
+ # bits prefix. If the string length is strictly less than 127, it is
56
+ # represented as one byte.
57
+ # * If the bit 7 of the first byte is 1, the string value is represented
58
+ # as a list of Huffman encoded octets
59
+ # (padded with bit 1's until next octet boundary).
60
+ # * If the bit 7 of the first byte is 0, the string value is
61
+ # represented as a list of UTF-8 encoded octets.
62
+ #
63
+ # +@options [:huffman]+ controls whether to use Huffman encoding:
64
+ # :never Do not use Huffman encoding
65
+ # :always Always use Huffman encoding
66
+ # :shorter Use Huffman when the result is strictly shorter
67
+ #
68
+ # @param str [String]
69
+ # @return [String] binary string
70
+ def string(str)
71
+ plain = nil
72
+ huffman = nil
73
+ plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY) unless @cc.options[:huffman] == :always
74
+ unless @cc.options[:huffman] == :never
75
+ huffman = Huffman.new.encode(str)
76
+ huffman = integer(huffman.bytesize, 7) << huffman
77
+ huffman.setbyte(0, huffman.ord | 0x80)
78
+ end
79
+ case @cc.options[:huffman]
80
+ when :always
81
+ huffman
82
+ when :never
83
+ plain
84
+ else
85
+ huffman.bytesize < plain.bytesize ? huffman : plain
86
+ end
87
+ end
88
+
89
+ # Encodes header command with appropriate header representation.
90
+ #
91
+ # @param h [Hash] header command
92
+ # @param buffer [String]
93
+ # @return [Buffer]
94
+ def header(h, buffer = Buffer.new)
95
+ rep = HEADREP[h[:type]]
96
+
97
+ case h[:type]
98
+ when :indexed
99
+ buffer << integer(h[:name] + 1, rep[:prefix])
100
+ when :changetablesize
101
+ buffer << integer(h[:value], rep[:prefix])
102
+ else
103
+ if h[:name].is_a? Integer
104
+ buffer << integer(h[:name] + 1, rep[:prefix])
105
+ else
106
+ buffer << integer(0, rep[:prefix])
107
+ buffer << string(h[:name])
108
+ end
109
+
110
+ buffer << string(h[:value])
111
+ end
112
+
113
+ # set header representation pattern on first byte
114
+ fb = buffer.ord | rep[:pattern]
115
+ buffer.setbyte(0, fb)
116
+
117
+ buffer
118
+ end
119
+
120
+ # Encodes provided list of HTTP headers.
121
+ #
122
+ # @param headers [Array] +[[name, value], ...]+
123
+ # @return [Buffer]
124
+ def encode(headers)
125
+ buffer = Buffer.new
126
+ pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ":" }
127
+ headers = [*pseudo_headers, *regular_headers]
128
+ commands = @cc.encode(headers)
129
+ commands.each do |cmd|
130
+ buffer << header(cmd)
131
+ end
132
+
133
+ buffer
134
+ end
135
+ end
136
+ end
137
+ end