http-2 0.12.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Machine generated Huffman decoder state machine.
2
4
  # DO NOT EDIT THIS FILE.
3
5
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP2
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/header/huffman"
32
+ require "http/2/header/huffman_statemachine"
33
+ require "http/2/header/encoding_context"
34
+ require "http/2/header/compressor"
35
+ require "http/2/header/decompressor"
data/lib/http/2/server.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
3
  module HTTP2
5
4
  # HTTP 2.0 server connection class that implements appropriate header
6
5
  # compression / decompression algorithms and stream management logic.
@@ -22,14 +21,19 @@ module HTTP2
22
21
  # end
23
22
  #
24
23
  class Server < Connection
24
+ attr_reader :origin_set
25
+
25
26
  # Initialize new HTTP 2.0 server object.
26
- def initialize(**settings)
27
+ def initialize(settings = {})
27
28
  @stream_id = 2
28
29
  @state = :waiting_magic
29
30
 
30
31
  @local_role = :server
31
32
  @remote_role = :client
32
33
 
34
+ @origin_set = []
35
+ @origins_sent = true
36
+
33
37
  super
34
38
  end
35
39
 
@@ -74,14 +78,17 @@ module HTTP2
74
78
  receive(CONNECTION_PREFACE_MAGIC)
75
79
 
76
80
  # Process received HTTP2-Settings payload
77
- buf = HTTP2::Buffer.new Base64.urlsafe_decode64(settings.to_s)
78
- header = @framer.common_header(
79
- length: buf.bytesize,
80
- type: :settings,
81
- stream: 0,
82
- flags: []
81
+ buf = "".b
82
+ buf << Base64.urlsafe_decode64(settings.to_s)
83
+ @framer.common_header(
84
+ {
85
+ length: buf.bytesize,
86
+ type: :settings,
87
+ stream: 0,
88
+ flags: []
89
+ },
90
+ buffer: buf
83
91
  )
84
- buf.prepend(header)
85
92
  receive(buf)
86
93
 
87
94
  # Activate stream (id: 1) with on HTTP/1.1 request parameters
@@ -90,6 +97,7 @@ module HTTP2
90
97
 
91
98
  headers_frame = {
92
99
  type: :headers,
100
+ flags: [:end_headers],
93
101
  stream: 1,
94
102
  weight: DEFAULT_WEIGHT,
95
103
  dependency: 0,
@@ -98,7 +106,7 @@ module HTTP2
98
106
  }
99
107
 
100
108
  if body.empty?
101
- headers_frame.merge!(flags: [:end_stream])
109
+ headers_frame[:flags] << [:end_stream]
102
110
  stream << headers_frame
103
111
  else
104
112
  stream << headers_frame
@@ -112,14 +120,31 @@ module HTTP2
112
120
  @state = :waiting_magic
113
121
  end
114
122
 
123
+ def origin_set=(origins)
124
+ @origin_set = Array(origins).map(&:to_s)
125
+ @origins_sent = @origin_set.empty?
126
+ end
127
+
115
128
  private
116
129
 
130
+ def connection_settings(frame)
131
+ super
132
+ return unless frame[:flags].include?(:ack) && !@origins_sent
133
+
134
+ send(type: :origin, stream: 0, payload: @origin_set)
135
+ end
136
+
137
+ def verify_pseudo_headers(frame)
138
+ _verify_pseudo_headers(frame, REQUEST_MANDATORY_HEADERS)
139
+ end
140
+
117
141
  # Handle locally initiated server-push event emitted by the stream.
118
142
  #
119
- # @param args [Array]
143
+ # @param parent [Stream]
144
+ # @param headers [Enumerable[String, String]]
145
+ # @param flags [Array[Symbol]]
120
146
  # @param callback [Proc]
121
- def promise(*args, &callback)
122
- parent, headers, flags = *args
147
+ def promise(parent, headers, flags)
123
148
  promise = new_stream(parent: parent)
124
149
  promise.send(
125
150
  type: :push_promise,
@@ -129,7 +154,7 @@ module HTTP2
129
154
  payload: headers.to_a
130
155
  )
131
156
 
132
- callback.call(promise)
157
+ yield(promise)
133
158
  end
134
159
  end
135
160
  end
data/lib/http/2/stream.rb CHANGED
@@ -74,19 +74,24 @@ module HTTP2
74
74
  # @param parent [Stream]
75
75
  # @param state [Symbol]
76
76
  def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
77
+ stream_error(:protocol_error, msg: "stream can't depend on itself") if id == dependency
78
+
77
79
  @connection = connection
78
80
  @id = id
79
81
  @weight = weight
80
82
  @dependency = dependency
81
- process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
83
+ process_priority(weight: weight, dependency: dependency, exclusive: exclusive)
82
84
  @local_window_max_size = connection.local_settings[:settings_initial_window_size]
83
- @local_window = connection.local_settings[:settings_initial_window_size]
85
+ @local_window = connection.local_settings[:settings_initial_window_size]
84
86
  @remote_window = connection.remote_settings[:settings_initial_window_size]
85
87
  @parent = parent
86
88
  @state = state
87
89
  @error = false
88
90
  @closed = false
89
- @send_buffer = []
91
+ @_method = @_content_length = @_status_code = nil
92
+ @_waiting_on_trailers = false
93
+ @received_data = false
94
+ @activated = false
90
95
 
91
96
  on(:window) { |v| @remote_window = v }
92
97
  on(:local_window) { |v| @local_window_max_size = @local_window = v }
@@ -104,18 +109,46 @@ module HTTP2
104
109
 
105
110
  case frame[:type]
106
111
  when :data
112
+ # 6.1. DATA
113
+ # If a DATA frame is received whose stream is not in "open" or
114
+ # "half closed (local)" state, the recipient MUST respond with a
115
+ # stream error (Section 5.4.2) of type STREAM_CLOSED.
116
+ stream_error(:stream_closed) unless @state == :open ||
117
+ @state == :half_closed_local ||
118
+ @state == :half_closing || @state == :closing ||
119
+ (@state == :closed && @closed == :local_rst)
120
+ @received_data = true
121
+ calculate_content_length(frame[:length])
107
122
  update_local_window(frame)
108
123
  # Emit DATA frame
109
124
  emit(:data, frame[:payload]) unless frame[:ignore]
110
125
  calculate_window_update(@local_window_max_size)
111
126
  when :headers
127
+ stream_error(:stream_closed) if (@state == :closed && @closed != :local_rst) ||
128
+ @state == :remote_closed
129
+ @_method ||= frame[:method]
130
+ @_status_code ||= frame[:status]
131
+ @_content_length ||= frame[:content_length]
132
+ @_trailers ||= frame[:trailer]
133
+ if @_waiting_on_trailers ||
134
+ (@received_data &&
135
+ (!@_status_code || @_status_code >= 200))
136
+
137
+ # An endpoint that receives a HEADERS frame without the END_STREAM flag set after receiving a final
138
+ # (non-informational) status code MUST treat the corresponding request or response as malformed.
139
+ verify_trailers(frame)
140
+ end
112
141
  emit(:headers, frame[:payload]) unless frame[:ignore]
142
+ @_waiting_on_trailers = !@_trailers.nil?
113
143
  when :push_promise
114
144
  emit(:promise_headers, frame[:payload]) unless frame[:ignore]
145
+ when :continuation
146
+ stream_error(:stream_closed) if (@state == :closed && @closed != :local_rst) || @state == :remote_closed
147
+ stream_error(:protocol_error) if @received_data
115
148
  when :priority
116
149
  process_priority(frame)
117
150
  when :window_update
118
- process_window_update(frame)
151
+ process_window_update(frame: frame)
119
152
  when :altsvc
120
153
  # 4. The ALTSVC HTTP/2 Frame
121
154
  # An ALTSVC frame on a
@@ -130,6 +163,29 @@ module HTTP2
130
163
  end
131
164
  alias << receive
132
165
 
166
+ def verify_trailers(frame)
167
+ stream_error(:protocol_error, msg: "trailer headers frame must close the stream") unless end_stream?(frame)
168
+ return unless @_trailers
169
+
170
+ trailers = frame[:payload]
171
+ return unless trailers.respond_to?(:each)
172
+
173
+ trailers.each do |field, _| # rubocop:disable Style/HashEachMethods
174
+ @_trailers.delete(field)
175
+ break if @_trailers.empty?
176
+ end
177
+ stream_error(:protocol_error, msg: "didn't receive all expected trailer headers") unless @_trailers.empty?
178
+ end
179
+
180
+ def calculate_content_length(data_length)
181
+ return unless @_content_length && data_length
182
+
183
+ @_content_length -= data_length
184
+ return if @_content_length >= 0
185
+
186
+ stream_error(:protocol_error, msg: "received more data than what was defined in content-length")
187
+ end
188
+
133
189
  # Processes outgoing HTTP 2.0 frames. Data frames may be automatically
134
190
  # split and buffered based on maximum frame size and current stream flow
135
191
  # control window size.
@@ -163,13 +219,13 @@ module HTTP2
163
219
  def headers(headers, end_headers: true, end_stream: false)
164
220
  flags = []
165
221
  flags << :end_headers if end_headers
166
- flags << :end_stream if end_stream
222
+ flags << :end_stream if end_stream || @_method == "HEAD"
167
223
 
168
224
  send(type: :headers, flags: flags, payload: headers)
169
225
  end
170
226
 
171
227
  def promise(headers, end_headers: true, &block)
172
- raise ArgumentError, 'must provide callback' unless block_given?
228
+ raise ArgumentError, "must provide callback" unless block
173
229
 
174
230
  flags = end_headers ? [:end_headers] : []
175
231
  emit(:promise, self, headers, flags, &block)
@@ -182,7 +238,7 @@ module HTTP2
182
238
  # @param dependency [Integer] new stream dependency stream
183
239
  def reprioritize(weight: 16, dependency: 0, exclusive: false)
184
240
  stream_error if @id.even?
185
- send(type: :priority, weight: weight, stream_dependency: dependency, exclusive: exclusive)
241
+ send(type: :priority, weight: weight, dependency: dependency, exclusive: exclusive)
186
242
  end
187
243
 
188
244
  # Sends DATA frame containing response payload.
@@ -550,7 +606,7 @@ module HTTP2
550
606
  case newstate
551
607
  when :open
552
608
  @state = newstate
553
- emit(:active)
609
+ activate_stream_in_conn
554
610
 
555
611
  when :reserved_local, :reserved_remote
556
612
  @state = newstate
@@ -558,7 +614,7 @@ module HTTP2
558
614
 
559
615
  when :half_closed_local, :half_closed_remote
560
616
  @closed = newstate
561
- emit(:active) unless @state == :open
617
+ activate_stream_in_conn unless @state == :open
562
618
  @state = :half_closing
563
619
 
564
620
  when :local_closed, :remote_closed, :local_rst, :remote_rst
@@ -569,11 +625,25 @@ module HTTP2
569
625
  @state
570
626
  end
571
627
 
628
+ # Streams that are in the "open" state, or either of the "half closed"
629
+ # states count toward the maximum number of streams that an endpoint is
630
+ # permitted to open.
631
+ def activate_stream_in_conn
632
+ @connection.active_stream_count += 1
633
+ @activated = true
634
+ emit(:active)
635
+ end
636
+
637
+ def close_stream_in_conn(*args)
638
+ @connection.active_stream_count -= 1 if @activated
639
+ emit(:close, *args)
640
+ end
641
+
572
642
  def complete_transition(frame)
573
643
  case @state
574
644
  when :closing
575
645
  @state = :closed
576
- emit(:close, frame[:error])
646
+ close_stream_in_conn(frame[:error])
577
647
  when :half_closing
578
648
  @state = @closed
579
649
  emit(:half_close)
@@ -582,11 +652,11 @@ module HTTP2
582
652
 
583
653
  def process_priority(frame)
584
654
  @weight = frame[:weight]
585
- @dependency = frame[:stream_dependency]
655
+ @dependency = frame[:dependency]
586
656
  emit(
587
657
  :priority,
588
658
  weight: frame[:weight],
589
- dependency: frame[:stream_dependency],
659
+ dependency: frame[:dependency],
590
660
  exclusive: frame[:exclusive]
591
661
  )
592
662
  # TODO: implement dependency tree housekeeping
@@ -599,19 +669,18 @@ module HTTP2
599
669
  def end_stream?(frame)
600
670
  case frame[:type]
601
671
  when :data, :headers, :continuation
602
- return false unless frame[:flags]
603
-
604
- frame[:flags].include?(:end_stream)
672
+ frame[:flags] && frame[:flags].include?(:end_stream)
605
673
  else false
606
674
  end
607
675
  end
608
676
 
609
677
  def stream_error(error = :internal_error, msg: nil)
678
+ # if the stream already broke with an error, ignore subsequent
679
+
610
680
  @error = error
611
681
  close(error) if @state != :closed
612
682
 
613
- klass = error.to_s.split('_').map(&:capitalize).join
614
- raise Error.const_get(klass), msg
683
+ raise Error.types[error], msg
615
684
  end
616
685
  alias error stream_error
617
686
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2
4
- VERSION = '0.12.0'
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/http/2.rb CHANGED
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'http/2/version'
4
- require 'http/2/error'
5
- require 'http/2/emitter'
6
- require 'http/2/buffer'
7
- require 'http/2/flow_buffer'
8
- require 'http/2/huffman'
9
- require 'http/2/huffman_statemachine'
10
- require 'http/2/compressor'
11
- require 'http/2/framer'
12
- require 'http/2/connection'
13
- require 'http/2/client'
14
- require 'http/2/server'
15
- require 'http/2/stream'
3
+ require "http/2/version"
4
+ require "http/2/extensions"
5
+ require "http/2/base64"
6
+ require "http/2/error"
7
+ require "http/2/emitter"
8
+ require "http/2/flow_buffer"
9
+ require "http/2/header"
10
+ require "http/2/framer"
11
+ require "http/2/connection"
12
+ require "http/2/client"
13
+ require "http/2/server"
14
+ require "http/2/stream"
data/sig/client.rbs ADDED
@@ -0,0 +1,9 @@
1
+ module HTTP2
2
+ class Client < Connection
3
+ def upgrade: () -> Stream
4
+
5
+ def send_connection_preface: () -> void
6
+
7
+ def self.settings_header: (settings_enum) -> String
8
+ end
9
+ end
@@ -0,0 +1,93 @@
1
+ module HTTP2
2
+ class Connection
3
+ include FlowBuffer
4
+ include Emitter
5
+
6
+ REQUEST_MANDATORY_HEADERS: Array[String]
7
+ RESPONSE_MANDATORY_HEADERS: Array[String]
8
+
9
+ attr_reader state: Symbol
10
+
11
+ attr_reader local_window: Integer
12
+ attr_reader remote_window: Integer
13
+
14
+ alias window local_window
15
+
16
+ attr_reader remote_settings: settings_hash
17
+ attr_reader local_settings: settings_hash
18
+ attr_reader pending_settings: settings_ary
19
+
20
+ attr_accessor active_stream_count: Integer
21
+
22
+ @stream_id: Integer
23
+ @active_stream_count: Integer
24
+ @last_activated_stream: Integer
25
+ @last_stream_id: Integer
26
+
27
+ @streams: Hash[Integer, Stream]
28
+ @streams_recently_closed: Hash[Integer, Stream]
29
+
30
+ @framer: Framer
31
+
32
+ @local_window_limit: Integer
33
+ @remote_window_limit: Integer
34
+
35
+ @compressor: Header::Compressor
36
+ @decompressor: Header::Decompressor
37
+ @error: Symbol?
38
+
39
+ @recv_buffer: String
40
+ @continuation: Array[frame]
41
+
42
+ @closed_since: Float?
43
+ @received_frame: bool
44
+
45
+ def closed?: () -> bool
46
+
47
+ def new_stream: (**untyped) -> Stream
48
+
49
+ def ping: (String) -> void
50
+ | (String) { () -> void } -> void
51
+
52
+ def goaway: (?Symbol, ?String) -> void
53
+
54
+ def window_update: (Integer increment) -> void
55
+
56
+ def settings: (settings_enum) -> void
57
+
58
+ def receive: (string data) -> void
59
+ alias << receive
60
+
61
+ def initialize: (?settings_hash) -> void
62
+
63
+ private
64
+
65
+ def send: (frame) -> void
66
+
67
+ def encode: (frame) -> Array[String]
68
+
69
+ def connection_frame?: (frame) -> bool
70
+
71
+ def connection_management: (frame) -> void
72
+
73
+ def ping_management: (frame) -> void
74
+
75
+ def validate_settings: (:client | :server, settings_enum) -> void
76
+
77
+ def connection_settings: (frame) -> void
78
+
79
+ def decode_headers: (frame) -> void
80
+
81
+ def encode_headers: (frame) -> Array[frame]
82
+
83
+ def activate_stream: (id: Integer, **untyped) -> Stream
84
+
85
+ def verify_stream_order: (Integer id) -> void
86
+
87
+ def verify_pseudo_headers: (frame) -> void
88
+
89
+ def _verify_pseudo_headers: (frame, Array[String]) -> void
90
+
91
+ def connection_error: (?Symbol error, ?msg: String?, ?e: StandardError?) -> void
92
+ end
93
+ end
data/sig/emitter.rbs ADDED
@@ -0,0 +1,13 @@
1
+ module HTTP2
2
+ module Emitter
3
+ def on: (Symbol event) { (*untyped) -> void } -> void
4
+
5
+ def once: (Symbol event) { (*untyped) -> void } -> void
6
+
7
+ def emit: (Symbol event, *untyped args) ?{ (*untyped) -> void } -> void
8
+
9
+ private
10
+
11
+ def listeners: (Symbol event) -> Array[Proc]
12
+ end
13
+ end
data/sig/error.rbs ADDED
@@ -0,0 +1,35 @@
1
+ module HTTP2
2
+ module Error
3
+ def self?.types: () -> Hash[Symbol, singleton(Error)]
4
+
5
+ class Error < StandardError
6
+ end
7
+
8
+ class HandshakeError < Error
9
+ end
10
+
11
+ class ProtocolError < Error
12
+ end
13
+
14
+ class CompressionError < ProtocolError
15
+ end
16
+
17
+ class FlowControlError < ProtocolError
18
+ end
19
+
20
+ class InternalError < ProtocolError
21
+ end
22
+
23
+ class StreamClosed < Error
24
+ end
25
+
26
+ class ConnectionClosed < Error
27
+ end
28
+
29
+ class StreamLimitExceeded < Error
30
+ end
31
+
32
+ class FrameSizeError < Error
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module HTTP2
2
+ module PackingExtensions
3
+ def pack: (Array[Integer] array_to_pack, String template, buffer: String, ?offset: Integer) -> String
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module HTTP2
2
+ module FlowBuffer
3
+ MAX_WINDOW_SIZE: Integer
4
+
5
+ def buffered_amount: () -> Integer
6
+
7
+ def flush: () -> void
8
+
9
+ private
10
+
11
+ def send_buffer: () -> FrameBuffer
12
+
13
+ def update_local_window: (frame) -> void
14
+
15
+ def calculate_window_update: (Integer) -> void
16
+
17
+ def send_data: (?frame? frame, ?bool encode) -> void
18
+
19
+ def process_window_update: (frame: frame, ?encode: bool) -> void
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module HTTP2
2
+ class FrameBuffer
3
+ attr_reader bytesize: Integer
4
+
5
+ @buffer: Array[data_frame]
6
+
7
+ def <<: (data_frame) -> void
8
+
9
+ def empty?: () -> bool
10
+
11
+ def retrieve: (Integer) -> data_frame?
12
+ end
13
+ end
data/sig/framer.rbs ADDED
@@ -0,0 +1,54 @@
1
+ module HTTP2
2
+ class Framer
3
+ include PackingExtensions
4
+
5
+ DEFAULT_MAX_FRAME_SIZE: Integer
6
+
7
+ MAX_STREAM_ID: Integer
8
+
9
+ MAX_WINDOWINC: Integer
10
+
11
+ FRAME_TYPES: Hash[Symbol, Integer]
12
+
13
+ FRAME_TYPES_WITH_PADDING: Array[Symbol]
14
+
15
+ FRAME_FLAGS: Hash[Symbol, Hash[Symbol, Integer]]
16
+
17
+ DEFINED_SETTINGS: Hash[Symbol, Integer]
18
+
19
+ DEFINED_ERRORS: Hash[Symbol, Integer]
20
+
21
+ RBIT: Integer
22
+ RBYTE: Integer
23
+ EBIT: Integer
24
+ UINT32: String
25
+ UINT16: String
26
+ UINT8: String
27
+ HEADERPACK: String
28
+ FRAME_LENGTH_HISHIFT: Integer
29
+ FRAME_LENGTH_LOMASK: Integer
30
+
31
+ @local_max_frame_size: Integer
32
+ @remote_max_frame_size: Integer
33
+
34
+ attr_accessor local_max_frame_size: Integer
35
+
36
+ attr_accessor remote_max_frame_size: Integer
37
+
38
+ def common_header: (frame, buffer: String) -> String
39
+
40
+ def read_common_frame: (String) -> frame
41
+
42
+ def generate: (frame) -> String
43
+
44
+ def parse: (String) -> frame?
45
+
46
+ private
47
+
48
+ def initialize: (?Integer local_max_frame_size, ?Integer remote_max_frame_size) -> untyped
49
+
50
+ def pack_error: (Integer | Symbol error, buffer: String) -> String
51
+
52
+ def unpack_error: (Integer) -> (Symbol | Integer)
53
+ end
54
+ end
@@ -0,0 +1,27 @@
1
+ module HTTP2
2
+ module Header
3
+ class Compressor
4
+ include PackingExtensions
5
+
6
+ @cc: EncodingContext
7
+
8
+ def table_size=: (Integer) -> void
9
+
10
+ def integer: (Integer, Integer, buffer: String, ?offset: Integer) -> String
11
+
12
+ def string: (String) -> String
13
+
14
+ def header: (header_command, ?String) -> String
15
+
16
+ def encode: (Enumerable[header_pair]) -> String
17
+
18
+ private
19
+
20
+ def initialize: (?context_hash options) -> void
21
+
22
+ def huffman_string: (String str) -> String
23
+
24
+ def plain_string: (String str) -> String
25
+ end
26
+ end
27
+ end