http-2-next 0.2.6 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba5613efe08f91c20db5d9c32fbbac18a2d6f214ba42037c341dd9b40cbffe23
4
- data.tar.gz: c7e0f3a0256a2cf7d106c1be72417c863e37f4c203bc7df6910ff91d135479e6
3
+ metadata.gz: aba29aa46e044dc37656a40df1cce81dcdc185fcb166d456592e5e05b5a45295
4
+ data.tar.gz: 95ada3112561c105b5d2a9e9545aab8a29809c49bd5a70d7c64690ce80b47ccc
5
5
  SHA512:
6
- metadata.gz: 645582d2208c23b84120256893828f8fc5f3c7d058848979fd62188a9fd31746aafade3759334d06ae8d53b2ee6ab3bff23d4eb5288112da31ccf6dee8a187cf
7
- data.tar.gz: 3804926e00e854b61a25dbf0e515ae5fcfb82532403909f6d3e4e035f7ebeef162c14bc00d64babd0013cdb22f2a7164f02dc708d1575392fc8b3445ddd953f6
6
+ metadata.gz: 3e0e50d2c307de9733f7951f9abb1a67c111da2b2740f04b7d204343410258981d28917dd0c22e7b8f6e6be9dec4bfb73f20fa118183a54268d1f4d2db43ce85
7
+ data.tar.gz: 4c5b8b4a840853e6d785b9d7565753e7e011a5db6d0b5b59fe042cd09491c10c429761a929824af83378b318d3eff43e58949ab6967bf57f6d571a89738ba77e
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
 
data/lib/http/2/next.rb CHANGED
@@ -4,11 +4,8 @@ require "http/2/next/version"
4
4
  require "http/2/next/extensions"
5
5
  require "http/2/next/error"
6
6
  require "http/2/next/emitter"
7
- require "http/2/next/buffer"
8
7
  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"
8
+ require "http/2/next/header"
12
9
  require "http/2/next/framer"
13
10
  require "http/2/next/connection"
14
11
  require "http/2/next/client"
@@ -51,18 +51,19 @@ module HTTP2Next
51
51
  include Emitter
52
52
  include Error
53
53
 
54
+ using StringExtensions
55
+
54
56
  # Connection state (:new, :closed).
55
57
  attr_reader :state
56
58
 
57
59
  # Size of current connection flow control window (by default, set to
58
60
  # infinity, but is automatically updated on receipt of peer settings).
59
61
  attr_reader :local_window
60
- attr_reader :remote_window
62
+ attr_reader :remote_window, :remote_settings
61
63
  alias window local_window
62
64
 
63
65
  # Current settings value for local and peer
64
66
  attr_reader :local_settings
65
- attr_reader :remote_settings
66
67
 
67
68
  # Pending settings value
68
69
  # Sent but not ack'ed settings
@@ -99,12 +100,13 @@ module HTTP2Next
99
100
  @remote_window_limit = @remote_settings[:settings_initial_window_size]
100
101
  @remote_window = @remote_window_limit
101
102
 
102
- @recv_buffer = Buffer.new
103
+ @recv_buffer = "".b
103
104
  @continuation = []
104
105
  @error = nil
105
106
 
106
107
  @h2c_upgrade = nil
107
108
  @closed_since = nil
109
+ @received_frame = false
108
110
  end
109
111
 
110
112
  def closed?
@@ -159,7 +161,7 @@ module HTTP2Next
159
161
  send(type: :goaway, last_stream: last_stream,
160
162
  error: error, payload: payload)
161
163
  @state = :closed
162
- @closed_since = Time.now
164
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
163
165
  end
164
166
 
165
167
  # Sends a WINDOW_UPDATE frame to the peer.
@@ -176,7 +178,7 @@ module HTTP2Next
176
178
  # @param settings [Array or Hash]
177
179
  def settings(payload)
178
180
  payload = payload.to_a
179
- connection_error if validate_settings(@local_role, payload)
181
+ validate_settings(@local_role, payload)
180
182
  @pending_settings << payload
181
183
  send(type: :settings, stream: 0, payload: payload)
182
184
  @pending_settings << payload
@@ -213,6 +215,11 @@ module HTTP2Next
213
215
  end
214
216
 
215
217
  while (frame = @framer.parse(@recv_buffer))
218
+ if is_a?(Client) && !@received_frame
219
+ connection_error(:protocol_error, msg: "didn't receive settings") if frame[:type] != :settings
220
+ @received_frame = true
221
+ end
222
+
216
223
  # Implementations MUST discard frames
217
224
  # that have unknown or unsupported types.
218
225
  if frame[:type].nil?
@@ -240,7 +247,7 @@ module HTTP2Next
240
247
  @continuation.clear
241
248
 
242
249
  frame.delete(:length)
243
- frame[:payload] = Buffer.new(payload)
250
+ frame[:payload] = payload
244
251
  frame[:flags] << :end_headers
245
252
  end
246
253
 
@@ -473,7 +480,7 @@ module HTTP2Next
473
480
  # the connection, although a new connection can be established
474
481
  # for new streams.
475
482
  @state = :closed
476
- @closed_since = Time.now
483
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
477
484
  emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
478
485
  when :altsvc
479
486
  # 4. The ALTSVC HTTP/2 Frame
@@ -498,7 +505,7 @@ module HTTP2Next
498
505
  when :ping
499
506
  ping_management(frame)
500
507
  else
501
- connection_error if (Time.now - @closed_since) > 15
508
+ connection_error if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @closed_since) > 15
502
509
  end
503
510
  else
504
511
  connection_error
@@ -521,8 +528,6 @@ module HTTP2Next
521
528
  def validate_settings(role, settings)
522
529
  settings.each do |key, v|
523
530
  case key
524
- when :settings_header_table_size
525
- # Any value is valid
526
531
  when :settings_enable_push
527
532
  case role
528
533
  when :server
@@ -530,32 +535,41 @@ module HTTP2Next
530
535
  # Clients MUST reject any attempt to change the
531
536
  # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
532
537
  # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
533
- return ProtocolError.new("invalid #{key} value") unless v.zero?
538
+ next if v.zero?
539
+
540
+ connection_error(:protocol_error, msg: "invalid #{key} value")
534
541
  when :client
535
542
  # Any value other than 0 or 1 MUST be treated as a
536
543
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
537
- return ProtocolError.new("invalid #{key} value") unless v.zero? || v == 1
544
+ next if v.zero? || v == 1
545
+
546
+ connection_error(:protocol_error, msg: "invalid #{key} value")
538
547
  end
539
- when :settings_max_concurrent_streams
540
- # Any value is valid
541
548
  when :settings_initial_window_size
542
549
  # Values above the maximum flow control window size of 2^31-1 MUST
543
550
  # be treated as a connection error (Section 5.4.1) of type
544
551
  # FLOW_CONTROL_ERROR.
545
- return FlowControlError.new("invalid #{key} value") unless v <= 0x7fffffff
552
+ next if v <= 0x7fffffff
553
+
554
+ connection_error(:flow_control_error, msg: "invalid #{key} value")
546
555
  when :settings_max_frame_size
547
556
  # The initial value is 2^14 (16,384) octets. The value advertised
548
557
  # by an endpoint MUST be between this initial value and the maximum
549
558
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
550
559
  # Values outside this range MUST be treated as a connection error
551
560
  # (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
561
+ next if v >= 16_384 && v <= 16_777_215
562
+
563
+ connection_error(:protocol_error, msg: "invalid #{key} value")
564
+ # when :settings_max_concurrent_streams
565
+ # Any value is valid
566
+ # when :settings_header_table_size
567
+ # Any value is valid
568
+ # when :settings_max_header_list_size
554
569
  # Any value is valid
555
570
  # else # ignore unknown settings
556
571
  end
557
572
  end
558
- nil
559
573
  end
560
574
 
561
575
  # Update connection settings based on parameters set by the peer.
@@ -572,8 +586,7 @@ module HTTP2Next
572
586
  # Process pending settings we have sent.
573
587
  [@pending_settings.shift, :local]
574
588
  else
575
- check = validate_settings(@remote_role, frame[:payload])
576
- connection_error(check) if check
589
+ validate_settings(@remote_role, frame[:payload])
577
590
  [frame[:payload], :remote]
578
591
  end
579
592
 
@@ -660,7 +673,7 @@ module HTTP2Next
660
673
  #
661
674
  # @param frame [Hash]
662
675
  def decode_headers(frame)
663
- frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a? Buffer
676
+ frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a?(String)
664
677
  rescue CompressionError => e
665
678
  connection_error(:compression_error, e: e)
666
679
  rescue ProtocolError => e
@@ -675,15 +688,16 @@ module HTTP2Next
675
688
  # @return [Array of Frame]
676
689
  def encode_headers(frame)
677
690
  payload = frame[:payload]
678
- payload = @compressor.encode(payload) unless payload.is_a? Buffer
691
+ payload = @compressor.encode(payload) unless payload.is_a?(String)
679
692
 
680
693
  frames = []
681
694
 
682
- while payload.bytesize > 0
695
+ while payload && payload.bytesize > 0
683
696
  cont = frame.dup
684
697
  cont[:type] = :continuation
685
698
  cont[:flags] = []
686
- cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
699
+ cont[:payload] = payload.byteslice(0, @remote_settings[:settings_max_frame_size])
700
+ payload = payload.byteslice(@remote_settings[:settings_max_frame_size]..-1)
687
701
  frames << cont
688
702
  end
689
703
  if frames.empty?
@@ -707,7 +721,7 @@ module HTTP2Next
707
721
  # @param priority [Integer]
708
722
  # @param window [Integer]
709
723
  # @param parent [Stream]
710
- def activate_stream(id: nil, **args)
724
+ def activate_stream(id:, **args)
711
725
  connection_error(msg: "Stream ID already exists") if @streams.key?(id)
712
726
 
713
727
  raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @local_settings[:settings_max_concurrent_streams])
@@ -724,11 +738,11 @@ module HTTP2Next
724
738
  # References to such streams will be purged whenever another stream
725
739
  # is closed, with a minimum of 15s RTT time window.
726
740
  @streams_recently_closed.reject! do |stream_id, v|
727
- to_delete = (Time.now - v) > 15
741
+ to_delete = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - v) > 15
728
742
  @streams.delete stream_id if to_delete
729
743
  to_delete
730
744
  end
731
- @streams_recently_closed[id] = Time.now
745
+ @streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
732
746
  end
733
747
 
734
748
  stream.on(:promise, &method(:promise)) if is_a? Server
@@ -746,7 +760,7 @@ module HTTP2Next
746
760
 
747
761
  def verify_pseudo_headers(frame, mandatory_headers)
748
762
  headers = frame[:payload]
749
- return if headers.is_a?(Buffer)
763
+ return if headers.is_a?(String)
750
764
 
751
765
  pseudo_headers = headers.take_while do |field, value|
752
766
  # use this loop to validate pseudo-headers
@@ -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
@@ -34,7 +33,7 @@ module HTTP2Next
34
33
  # @param block [Proc] callback function
35
34
  def emit(event, *args, &block)
36
35
  listeners(event).delete_if do |cb|
37
- cb.call(*args, &block) == :delete
36
+ :delete == cb.call(*args, &block) # rubocop:disable Style/YodaCondition
38
37
  end
39
38
  end
40
39
 
@@ -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,31 @@ module HTTP2Next
10
10
  end
11
11
  end
12
12
  end
13
+
14
+ module StringExtensions
15
+ refine String do
16
+ def read(n)
17
+ return "".b if n == 0
18
+
19
+ chunk = byteslice(0..n - 1)
20
+ remaining = byteslice(n..-1)
21
+ remaining ? replace(remaining) : clear
22
+ chunk
23
+ end
24
+
25
+ def read_uint32
26
+ read(4).unpack1("N")
27
+ end
28
+
29
+ def shift_byte
30
+ read(1).ord
31
+ end
32
+
33
+ unless String.method_defined?(:unpack1)
34
+ def unpack1(format)
35
+ unpack(format).first
36
+ end
37
+ end
38
+ end
39
+ end
13
40
  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
@@ -137,10 +138,13 @@ module HTTP2Next
137
138
 
138
139
  # Split frame so that it fits in the window
139
140
  # TODO: consider padding!
140
- frame[:payload] = payload.slice!(0, window_size)
141
- frame[:length] = frame[:payload].bytesize
142
- chunk[:length] = payload.bytesize
141
+ frame_bytes = payload.byteslice(0, window_size)
142
+ payload = payload.byteslice(window_size..-1)
143
+
144
+ frame[:payload] = frame_bytes
145
+ frame[:length] = frame_bytes.bytesize
143
146
  chunk[:payload] = payload
147
+ chunk[:length] = payload.bytesize
144
148
 
145
149
  # if no longer last frame in sequence...
146
150
  frame[:flags] -= [:end_stream] if end_stream
@@ -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,9 +156,10 @@ 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
- len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
162
+ len_hi, len_lo, type, flags, stream = buf.byteslice(0, 9).unpack(HEADERPACK)
160
163
 
161
164
  frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
162
165
  frame[:type], = FRAME_TYPES.find { |_t, pos| type == pos }
@@ -175,7 +178,7 @@ module HTTP2Next
175
178
  #
176
179
  # @param frame [Hash]
177
180
  def generate(frame)
178
- bytes = Buffer.new
181
+ bytes = "".b
179
182
  length = 0
180
183
 
181
184
  frame[:flags] ||= []
@@ -184,6 +187,7 @@ module HTTP2Next
184
187
  case frame[:type]
185
188
  when :data
186
189
  bytes << frame[:payload]
190
+ bytes.force_encoding(Encoding::BINARY)
187
191
  length += frame[:payload].bytesize
188
192
 
189
193
  when :headers
@@ -221,7 +225,7 @@ module HTTP2Next
221
225
  raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
222
226
 
223
227
  frame[:payload].each do |(k, v)|
224
- if k.is_a? Integer
228
+ if k.is_a? Integer # rubocop:disable Style/GuardClause
225
229
  DEFINED_SETTINGS.value?(k) || next
226
230
  else
227
231
  k = DEFINED_SETTINGS[k]
@@ -333,10 +337,10 @@ module HTTP2Next
333
337
  #
334
338
  # @param buf [Buffer]
335
339
  def parse(buf)
336
- return nil if buf.size < 9
340
+ return if buf.size < 9
337
341
 
338
342
  frame = read_common_header(buf)
339
- return nil if buf.size < 9 + frame[:length]
343
+ return if buf.size < 9 + frame[:length]
340
344
 
341
345
  raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
342
346
 
@@ -353,11 +357,11 @@ module HTTP2Next
353
357
  if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
354
358
  padded = frame[:flags].include?(:padded)
355
359
  if padded
356
- padlen = payload.read(1).unpack(UINT8).first
360
+ padlen = payload.read(1).unpack1(UINT8)
357
361
  frame[:padding] = padlen + 1
358
362
  raise ProtocolError, "padding too long" if padlen > payload.bytesize
359
363
 
360
- payload.slice!(-padlen, padlen) if padlen > 0
364
+ payload = payload.byteslice(0, payload.bytesize - padlen) if padlen > 0
361
365
  frame[:length] -= frame[:padding]
362
366
  frame[:flags].delete(:padded)
363
367
  end
@@ -371,7 +375,9 @@ module HTTP2Next
371
375
  e_sd = payload.read_uint32
372
376
  frame[:dependency] = e_sd & RBIT
373
377
  frame[:exclusive] = (e_sd & EBIT) != 0
374
- frame[:weight] = payload.getbyte + 1
378
+ weight = payload.byteslice(0, 1).ord + 1
379
+ frame[:weight] = weight
380
+ payload = payload.byteslice(1..-1)
375
381
  end
376
382
  frame[:payload] = payload.read(frame[:length])
377
383
  when :priority
@@ -380,7 +386,9 @@ module HTTP2Next
380
386
  e_sd = payload.read_uint32
381
387
  frame[:dependency] = e_sd & RBIT
382
388
  frame[:exclusive] = (e_sd & EBIT) != 0
383
- frame[:weight] = payload.getbyte + 1
389
+ weight = payload.byteslice(0, 1).ord + 1
390
+ frame[:weight] = weight
391
+ payload = payload.byteslice(1..-1)
384
392
  when :rst_stream
385
393
  raise FrameSizeError, "Invalid length for RST_STREAM (#{frame[:length]} != 4)" if frame[:length] != 4
386
394
 
@@ -395,7 +403,7 @@ module HTTP2Next
395
403
  raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
396
404
 
397
405
  (frame[:length] / 6).times do
398
- id = payload.read(2).unpack(UINT16).first
406
+ id = payload.read(2).unpack1(UINT16)
399
407
  val = payload.read_uint32
400
408
 
401
409
  # Unsupported or unrecognized settings MUST be ignored.
@@ -425,10 +433,12 @@ module HTTP2Next
425
433
  when :altsvc
426
434
  frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
427
435
 
428
- len = payload.getbyte
436
+ len = payload.byteslice(0, 1).ord
437
+ payload = payload.byteslice(1..-1)
429
438
  frame[:proto] = payload.read(len) if len > 0
430
439
 
431
- len = payload.getbyte
440
+ len = payload.byteslice(0, 1).ord
441
+ payload = payload.byteslice(1..-1)
432
442
  frame[:host] = payload.read(len) if len > 0
433
443
 
434
444
  frame[:origin] = payload.read(payload.size) unless payload.empty?
@@ -437,7 +447,7 @@ module HTTP2Next
437
447
  origins = []
438
448
 
439
449
  until payload.empty?
440
- len = payload.read(2).unpack(UINT16).first
450
+ len = payload.read(2).unpack1(UINT16)
441
451
  origins << payload.read(len)
442
452
  end
443
453
 
@@ -452,17 +462,16 @@ module HTTP2Next
452
462
 
453
463
  def pack_error(e)
454
464
  unless e.is_a? Integer
455
- raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
456
-
457
465
  e = DEFINED_ERRORS[e]
466
+
467
+ raise CompressionError, "Unknown error ID for #{e}" unless e
458
468
  end
459
469
 
460
470
  [e].pack(UINT32)
461
471
  end
462
472
 
463
473
  def unpack_error(error)
464
- name, = DEFINED_ERRORS.find { |_name, v| v == error }
465
- name || error
474
+ DEFINED_ERRORS.key(error) || error
466
475
  end
467
476
  end
468
477
  end