http-2 0.6.1 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,36 +11,15 @@ module HTTP2
11
11
 
12
12
  # Connection encapsulates all of the connection, stream, flow-control,
13
13
  # error management, and other processing logic required for a well-behaved
14
- # HTTP 2.0 client.
15
- #
16
- # When the connection object is instantiated you must specify its role
17
- # (:client or :server) to initialize appropriate header compression
18
- # and decompression algorithms and stream management logic.
19
- #
20
- # Your code is responsible for feeding data to connection object, which
21
- # performs all of the necessary HTTP 2.0 decoding, state management and
22
- # the rest, and vice versa, the parser will emit bytes (encoded HTTP 2.0
23
- # frames) that you can then route to the destination. Roughly, this works
24
- # as follows:
25
- #
26
- # @example
27
- # socket = YourTransport.new
28
- #
29
- # conn = HTTP2::Connection.new(:client)
30
- # conn.on(:frame) {|bytes| socket << bytes }
31
- #
32
- # while bytes = socket.read
33
- # conn << bytes
34
- # end
14
+ # HTTP 2.0 endpoint.
35
15
  #
16
+ # Note that this class should not be used directly. Instead, you want to
17
+ # use either Client or Server class to drive the HTTP 2.0 exchange.
36
18
  class Connection
37
19
  include FlowBuffer
38
20
  include Emitter
39
21
  include Error
40
22
 
41
- # Type of connection (:server, :client).
42
- attr_reader :type
43
-
44
23
  # Connection state (:new, :closed).
45
24
  attr_reader :state
46
25
 
@@ -59,34 +38,20 @@ module HTTP2
59
38
  # are not counted towards the stream limit).
60
39
  attr_reader :active_stream_count
61
40
 
62
- # Initializes new client or server connection object.
41
+ # Initializes new connection object.
63
42
  #
64
- # @param type [Symbol]
65
- def initialize(type = :client)
66
- @type = type
67
-
68
- if @type == :server
69
- @stream_id = 2
70
- @compressor = Header::Compressor.new(:response)
71
- @decompressor = Header::Decompressor.new(:request)
72
- else
73
- @stream_id = 1
74
- @compressor = Header::Compressor.new(:request)
75
- @decompressor = Header::Decompressor.new(:response)
76
- end
77
-
78
- @stream_limit = Float::INFINITY
43
+ def initialize(streams: 100, window: DEFAULT_FLOW_WINDOW)
44
+ @stream_limit = streams
79
45
  @active_stream_count = 0
80
46
  @streams = {}
81
47
 
82
48
  @framer = Framer.new
83
- @window = DEFAULT_FLOW_WINDOW
84
- @window_limit = DEFAULT_FLOW_WINDOW
49
+ @window = window
50
+ @window_limit = window
85
51
 
86
52
  @recv_buffer = Buffer.new
87
53
  @send_buffer = []
88
54
  @continuation = []
89
- @state = :new
90
55
  @error = nil
91
56
  end
92
57
 
@@ -95,11 +60,11 @@ module HTTP2
95
60
  # @param priority [Integer]
96
61
  # @param window [Integer]
97
62
  # @param parent [Stream]
98
- def new_stream(priority: DEFAULT_PRIORITY, window: @window_limit, parent: nil)
63
+ def new_stream(priority: DEFAULT_PRIORITY, parent: nil)
99
64
  raise ConnectionClosed.new if @state == :closed
100
65
  raise StreamLimitExceeded.new if @active_stream_count == @stream_limit
101
66
 
102
- stream = activate_stream(@stream_id, priority, window, parent)
67
+ stream = activate_stream(@stream_id, priority, parent)
103
68
  @stream_id += 2
104
69
 
105
70
  stream
@@ -132,13 +97,19 @@ module HTTP2
132
97
  @state = :closed
133
98
  end
134
99
 
135
- # Sends a connection SETTINGS frame to the peer.
100
+ # Sends a connection SETTINGS frame to the peer. Setting window size
101
+ # to Float::INFINITY disables flow control.
136
102
  #
137
- # @param payload [Hash]
138
- # @option payload [Symbol] :settings_max_concurrent_streams
139
- # @option payload [Symbol] :settings_flow_control_options
140
- # @option payload [Symbol] :settings_initial_window_size
141
- def settings(payload)
103
+ # @param stream_limit [Integer] maximum number of concurrent streams
104
+ # @param window_limit [Float] maximum flow window size
105
+ def settings(stream_limit: @stream_limit, window_limit: @window_limit)
106
+ payload = { settings_max_concurrent_streams: stream_limit }
107
+ if window_limit.to_f.infinite?
108
+ payload[:settings_flow_control_options] = 1
109
+ else
110
+ payload[:settings_initial_window_size] = window_limit
111
+ end
112
+
142
113
  send({type: :settings, stream: 0, payload: payload})
143
114
  end
144
115
 
@@ -150,6 +121,29 @@ module HTTP2
150
121
  def receive(data)
151
122
  @recv_buffer << data
152
123
 
124
+ # Upon establishment of a TCP connection and determination that
125
+ # HTTP/2.0 will be used by both peers, each endpoint MUST send a
126
+ # connection header as a final confirmation and to establish the
127
+ # initial settings for the HTTP/2.0 connection.
128
+ #
129
+ # Client connection header is 24 byte connection header followed by
130
+ # SETTINGS frame. Server connection header is SETTINGS frame only.
131
+ if @state == :new
132
+ if @recv_buffer.size < 24
133
+ if !CONNECTION_HEADER.start_with? @recv_buffer
134
+ raise HandshakeError.new
135
+ else
136
+ return
137
+ end
138
+
139
+ elsif @recv_buffer.read(24) != CONNECTION_HEADER
140
+ raise HandshakeError.new
141
+ else
142
+ @state = :connection_header
143
+ settings(stream_limit: @stream_limit, window_limit: @window_limit)
144
+ end
145
+ end
146
+
153
147
  while frame = @framer.parse(@recv_buffer) do
154
148
  # Header blocks MUST be transmitted as a contiguous sequence of frames
155
149
  # with no interleaved frames of any other type, or from any other stream.
@@ -207,8 +201,7 @@ module HTTP2
207
201
  stream = @streams[frame[:stream]]
208
202
  if stream.nil?
209
203
  stream = activate_stream(frame[:stream],
210
- frame[:priority] || DEFAULT_PRIORITY,
211
- @window_limit)
204
+ frame[:priority] || DEFAULT_PRIORITY)
212
205
  emit(:stream, stream)
213
206
  end
214
207
 
@@ -255,7 +248,7 @@ module HTTP2
255
248
  end
256
249
  end
257
250
 
258
- stream = activate_stream(pid, DEFAULT_PRIORITY, @window_limit, parent)
251
+ stream = activate_stream(pid, DEFAULT_PRIORITY, parent)
259
252
  emit(:promise, stream)
260
253
  stream << frame
261
254
  else
@@ -269,6 +262,9 @@ module HTTP2
269
262
  end
270
263
  end
271
264
  end
265
+
266
+ rescue
267
+ connection_error
272
268
  end
273
269
  alias :<< :receive
274
270
 
@@ -300,7 +296,7 @@ module HTTP2
300
296
  # Applies HTTP 2.0 binary encoding to the frame.
301
297
  #
302
298
  # @param frame [Hash]
303
- # @return [String] encoded frame
299
+ # @return [Buffer] encoded frame
304
300
  def encode(frame)
305
301
  if frame[:type] == :headers ||
306
302
  frame[:type] == :push_promise
@@ -331,7 +327,7 @@ module HTTP2
331
327
  # @param frame [Hash]
332
328
  def connection_management(frame)
333
329
  case @state
334
- when :new
330
+ when :connection_header
335
331
  # SETTINGS frames MUST be sent at the start of a connection.
336
332
  connection_settings(frame)
337
333
  @state = :connected
@@ -419,7 +415,7 @@ module HTTP2
419
415
  # @param frame [Hash]
420
416
  def decode_headers(frame)
421
417
  if frame[:payload].is_a? String
422
- frame[:payload] = @decompressor.decode(StringIO.new(frame[:payload]))
418
+ frame[:payload] = @decompressor.decode(frame[:payload])
423
419
  end
424
420
 
425
421
  rescue Exception => e
@@ -453,46 +449,24 @@ module HTTP2
453
449
  # @param priority [Integer]
454
450
  # @param window [Integer]
455
451
  # @param parent [Stream]
456
- def activate_stream(id, priority, window, parent = nil)
452
+ def activate_stream(id, priority, parent = nil)
457
453
  if @streams.key?(id)
458
454
  connection_error(msg: 'Stream ID already exists')
459
455
  end
460
456
 
461
- stream = Stream.new(id, priority, window, parent)
457
+ stream = Stream.new(id, priority, @window_limit, parent)
462
458
 
463
459
  # Streams that are in the "open" state, or either of the "half closed"
464
460
  # states count toward the maximum number of streams that an endpoint is
465
461
  # permitted to open.
466
462
  stream.once(:active) { @active_stream_count += 1 }
467
463
  stream.once(:close) { @active_stream_count -= 1 }
468
- stream.on(:promise, &method(:promise))
464
+ stream.on(:promise, &method(:promise)) if self.is_a? Server
469
465
  stream.on(:frame, &method(:send))
470
466
 
471
467
  @streams[id] = stream
472
468
  end
473
469
 
474
- # Handle locally initiated server-push event emitted by the stream.
475
- #
476
- # @param args [Array]
477
- # @param callback [Proc]
478
- def promise(*args, &callback)
479
- if @type == :client
480
- raise ProtocolError.new("client cannot initiate promise")
481
- end
482
-
483
- parent, headers, flags = *args
484
- promise = new_stream(parent: parent)
485
- promise.send({
486
- type: :push_promise,
487
- flags: flags,
488
- stream: parent.id,
489
- promise_stream: promise.id,
490
- payload: headers.to_a
491
- })
492
-
493
- callback.call(promise)
494
- end
495
-
496
470
  # Emit GOAWAY error indicating to peer that the connection is being
497
471
  # aborted, and once sent, raise a local exception.
498
472
  #
@@ -509,7 +483,7 @@ module HTTP2
509
483
 
510
484
  @state, @error = :closed, error
511
485
  klass = error.to_s.split('_').map(&:capitalize).join
512
- raise Kernel.const_get(klass).new(msg)
486
+ raise Error.const_get(klass).new(msg)
513
487
  end
514
488
 
515
489
  end
@@ -20,8 +20,8 @@ module HTTP2
20
20
  # @param event [Symbol]
21
21
  # @param block [Proc] callback function
22
22
  def once(event, &block)
23
- add_listener(event) do |*args|
24
- block.call(*args)
23
+ add_listener(event) do |*args, &callback|
24
+ block.call(*args, &callback)
25
25
  :delete
26
26
  end
27
27
  end
@@ -3,6 +3,11 @@ module HTTP2
3
3
  # Stream, connection, and compressor exceptions.
4
4
  module Error
5
5
 
6
+ # Raised if connection header is missing or invalid indicating that
7
+ # this is an invalid HTTP 2.0 request - no frames are emitted and the
8
+ # connection must be aborted.
9
+ class HandshakeError < Exception; end
10
+
6
11
  # Raised by stream or connection handlers, results in GOAWAY frame
7
12
  # which signals termination of the current connection. You *cannot*
8
13
  # recover from this exception, or any exceptions subclassed from it.
@@ -71,8 +71,8 @@ module HTTP2
71
71
 
72
72
  RBIT = 0x7fffffff
73
73
  RBYTE = 0x0fffffff
74
- HEADERPACK = "SCCL"
75
- UINT32 = "L"
74
+ HEADERPACK = "nCCN"
75
+ UINT32 = "N"
76
76
 
77
77
  private_constant :RBIT, :RBYTE, :HEADERPACK, :UINT32
78
78
 
@@ -118,7 +118,7 @@ module HTTP2
118
118
 
119
119
  # Decodes common 8-byte header.
120
120
  #
121
- # @param buf [String]
121
+ # @param buf [Buffer]
122
122
  def readCommonHeader(buf)
123
123
  frame = {}
124
124
  frame[:length], type, flags, stream = buf.slice(0,8).unpack(HEADERPACK)
@@ -138,7 +138,7 @@ module HTTP2
138
138
  #
139
139
  # @param frame [Hash]
140
140
  def generate(frame)
141
- bytes = ''
141
+ bytes = Buffer.new
142
142
  length = 0
143
143
 
144
144
  frame[:flags] ||= []
@@ -146,7 +146,7 @@ module HTTP2
146
146
 
147
147
  case frame[:type]
148
148
  when :data
149
- bytes += frame[:payload]
149
+ bytes << frame[:payload]
150
150
  length += frame[:payload].bytesize
151
151
 
152
152
  when :headers
@@ -155,19 +155,19 @@ module HTTP2
155
155
  end
156
156
 
157
157
  if frame[:flags].include? :priority
158
- bytes += [frame[:priority] & RBIT].pack(UINT32)
158
+ bytes << [frame[:priority] & RBIT].pack(UINT32)
159
159
  length += 4
160
160
  end
161
161
 
162
- bytes += frame[:payload]
162
+ bytes << frame[:payload]
163
163
  length += frame[:payload].bytesize
164
164
 
165
165
  when :priority
166
- bytes += [frame[:priority] & RBIT].pack(UINT32)
166
+ bytes << [frame[:priority] & RBIT].pack(UINT32)
167
167
  length += 4
168
168
 
169
169
  when :rst_stream
170
- bytes += pack_error frame[:error]
170
+ bytes << pack_error(frame[:error])
171
171
  length += 4
172
172
 
173
173
  when :settings
@@ -184,14 +184,14 @@ module HTTP2
184
184
  end
185
185
  end
186
186
 
187
- bytes += [k & RBYTE].pack(UINT32)
188
- bytes += [v].pack(UINT32)
187
+ bytes << [k & RBYTE].pack(UINT32)
188
+ bytes << [v].pack(UINT32)
189
189
  length += 8
190
190
  end
191
191
 
192
192
  when :push_promise
193
- bytes += [frame[:promise_stream] & RBIT].pack(UINT32)
194
- bytes += frame[:payload]
193
+ bytes << [frame[:promise_stream] & RBIT].pack(UINT32)
194
+ bytes << frame[:payload]
195
195
  length += 4 + frame[:payload].bytesize
196
196
 
197
197
  when :ping
@@ -199,36 +199,36 @@ module HTTP2
199
199
  raise CompressionError.new("Invalid payload size \
200
200
  (#{frame[:payload].size} != 8 bytes)")
201
201
  end
202
- bytes += frame[:payload]
202
+ bytes << frame[:payload]
203
203
  length += 8
204
204
 
205
205
  when :goaway
206
- bytes += [frame[:last_stream] & RBIT].pack(UINT32)
207
- bytes += pack_error frame[:error]
206
+ bytes << [frame[:last_stream] & RBIT].pack(UINT32)
207
+ bytes << pack_error(frame[:error])
208
208
  length += 8
209
209
 
210
210
  if frame[:payload]
211
- bytes += frame[:payload]
211
+ bytes << frame[:payload]
212
212
  length += frame[:payload].bytesize
213
213
  end
214
214
 
215
215
  when :window_update
216
- bytes += [frame[:increment] & RBIT].pack(UINT32)
216
+ bytes << [frame[:increment] & RBIT].pack(UINT32)
217
217
  length += 4
218
218
 
219
219
  when :continuation
220
- bytes += frame[:payload]
220
+ bytes << frame[:payload]
221
221
  length += frame[:payload].bytesize
222
222
  end
223
223
 
224
224
  frame[:length] = length
225
- commonHeader(frame) + bytes
225
+ bytes.prepend(commonHeader(frame))
226
226
  end
227
227
 
228
228
  # Decodes complete HTTP 2.0 frame from provided buffer. If the buffer
229
229
  # does not contain enough data, no further work is performed.
230
230
  #
231
- # @param buf [String]
231
+ # @param buf [Buffer]
232
232
  def parse(buf)
233
233
  return nil if buf.size < 8
234
234
  frame = readCommonHeader(buf)
@@ -242,36 +242,37 @@ module HTTP2
242
242
  frame[:payload] = payload.read(frame[:length])
243
243
  when :headers
244
244
  if frame[:flags].include? :priority
245
- frame[:priority] = payload.read(4).unpack(UINT32).first & RBIT
245
+ frame[:priority] = payload.read_uint32 & RBIT
246
246
  end
247
247
  frame[:payload] = payload.read(frame[:length])
248
248
  when :priority
249
- frame[:priority] = payload.read(4).unpack(UINT32).first & RBIT
249
+ frame[:priority] = payload.read_uint32 & RBIT
250
250
  when :rst_stream
251
- frame[:error] = unpack_error payload.read(4).unpack(UINT32).first
251
+ frame[:error] = unpack_error payload.read_uint32
252
252
 
253
253
  when :settings
254
254
  frame[:payload] = {}
255
255
  (frame[:length] / 8).times do
256
- id = payload.read(4).unpack(UINT32).first & RBYTE
257
- val = payload.read(4).unpack(UINT32).first
256
+ id = payload.read_uint32 & RBYTE
257
+ val = payload.read_uint32
258
258
 
259
+ # Unsupported or unrecognized settings MUST be ignored.
259
260
  name, _ = DEFINED_SETTINGS.select { |name, v| v == id }.first
260
- frame[:payload][name || id] = val
261
+ frame[:payload][name] = val if name
261
262
  end
262
263
  when :push_promise
263
- frame[:promise_stream] = payload.read(4).unpack(UINT32).first & RBIT
264
+ frame[:promise_stream] = payload.read_uint32 & RBIT
264
265
  frame[:payload] = payload.read(frame[:length])
265
266
  when :ping
266
267
  frame[:payload] = payload.read(frame[:length])
267
268
  when :goaway
268
- frame[:last_stream] = payload.read(4).unpack(UINT32).first & RBIT
269
- frame[:error] = unpack_error payload.read(4).unpack(UINT32).first
269
+ frame[:last_stream] = payload.read_uint32 & RBIT
270
+ frame[:error] = unpack_error payload.read_uint32
270
271
 
271
272
  size = frame[:length] - 8
272
273
  frame[:payload] = payload.read(size) if size > 0
273
274
  when :window_update
274
- frame[:increment] = payload.read(4).unpack(UINT32).first & RBIT
275
+ frame[:increment] = payload.read_uint32 & RBIT
275
276
  when :continuation
276
277
  frame[:payload] = payload.read(frame[:length])
277
278
  end