http-2-next 0.2.6 → 0.4.3

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