mieps_http-2 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,654 @@
1
+ module HTTP2
2
+ # Default connection and stream flow control window (64KB).
3
+ DEFAULT_FLOW_WINDOW = 65_535
4
+
5
+ # Default header table size
6
+ DEFAULT_HEADER_SIZE = 4096
7
+
8
+ # Default stream_limit
9
+ DEFAULT_MAX_CONCURRENT_STREAMS = 100
10
+
11
+ # Default values for SETTINGS frame, as defined by the spec.
12
+ SPEC_DEFAULT_CONNECTION_SETTINGS = {
13
+ settings_header_table_size: 4096,
14
+ settings_enable_push: 1, # enabled for servers
15
+ settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
16
+ settings_initial_window_size: 65_535,
17
+ settings_max_frame_size: 16_384,
18
+ settings_max_header_list_size: 2**31 - 1, # unlimited
19
+ }.freeze
20
+
21
+ DEFAULT_CONNECTION_SETTINGS = {
22
+ settings_header_table_size: 4096,
23
+ settings_enable_push: 1, # enabled for servers
24
+ settings_max_concurrent_streams: 100,
25
+ settings_initial_window_size: 65_535, #
26
+ settings_max_frame_size: 16_384,
27
+ settings_max_header_list_size: 2**31 - 1, # unlimited
28
+ }.freeze
29
+
30
+ # Default stream priority (lower values are higher priority).
31
+ DEFAULT_WEIGHT = 16
32
+
33
+ # Default connection "fast-fail" preamble string as defined by the spec.
34
+ CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
35
+
36
+ # Connection encapsulates all of the connection, stream, flow-control,
37
+ # error management, and other processing logic required for a well-behaved
38
+ # HTTP 2.0 endpoint.
39
+ #
40
+ # Note that this class should not be used directly. Instead, you want to
41
+ # use either Client or Server class to drive the HTTP 2.0 exchange.
42
+ #
43
+ # rubocop:disable ClassLength
44
+ class Connection
45
+ include FlowBuffer
46
+ include Emitter
47
+ include Error
48
+
49
+ # Connection state (:new, :closed).
50
+ attr_reader :state
51
+
52
+ # Last connection error if connection is aborted.
53
+ attr_reader :error
54
+
55
+ # Size of current connection flow control window (by default, set to
56
+ # infinity, but is automatically updated on receipt of peer settings).
57
+ attr_reader :local_window
58
+ attr_reader :remote_window
59
+ alias_method :window, :local_window
60
+
61
+ # Current settings value for local and peer
62
+ attr_reader :local_settings
63
+ attr_reader :remote_settings
64
+
65
+ # Pending settings value
66
+ # Sent but not ack'ed settings
67
+ attr_reader :pending_settings
68
+
69
+ # Number of active streams between client and server (reserved streams
70
+ # are not counted towards the stream limit).
71
+ attr_reader :active_stream_count
72
+
73
+ # Initializes new connection object.
74
+ #
75
+ def initialize(**settings)
76
+ @local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
77
+ @remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
78
+
79
+ @compressor = Header::Compressor.new(settings)
80
+ @decompressor = Header::Decompressor.new(settings)
81
+
82
+ @active_stream_count = 0
83
+ @streams = {}
84
+ @pending_settings = []
85
+
86
+ @framer = Framer.new
87
+
88
+ @local_window_limit = @local_settings[:settings_initial_window_size]
89
+ @local_window = @local_window_limit
90
+ @remote_window_limit = @remote_settings[:settings_initial_window_size]
91
+ @remote_window = @remote_window_limit
92
+
93
+ @recv_buffer = Buffer.new
94
+ @send_buffer = []
95
+ @continuation = []
96
+ @error = nil
97
+ end
98
+
99
+ # Allocates new stream for current connection.
100
+ #
101
+ # @param priority [Integer]
102
+ # @param window [Integer]
103
+ # @param parent [Stream]
104
+ def new_stream(**args)
105
+ fail ConnectionClosed if @state == :closed
106
+ fail StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
107
+
108
+ stream = activate_stream(id: @stream_id, **args)
109
+ @stream_id += 2
110
+
111
+ stream
112
+ end
113
+
114
+ # Sends PING frame to the peer.
115
+ #
116
+ # @param payload [String] optional payload must be 8 bytes long
117
+ # @param blk [Proc] callback to execute when PONG is received
118
+ def ping(payload, &blk)
119
+ send(type: :ping, stream: 0, payload: payload)
120
+ once(:ack, &blk) if blk
121
+ end
122
+
123
+ # Sends a GOAWAY frame indicating that the peer should stop creating
124
+ # new streams for current connection.
125
+ #
126
+ # Endpoints MAY append opaque data to the payload of any GOAWAY frame.
127
+ # Additional debug data is intended for diagnostic purposes only and
128
+ # carries no semantic value. Debug data MUST NOT be persistently stored,
129
+ # since it could contain sensitive information.
130
+ #
131
+ # @param error [Symbol]
132
+ # @param payload [String]
133
+ def goaway(error = :no_error, payload = nil)
134
+ last_stream = if (max = @streams.max)
135
+ max.first
136
+ else
137
+ 0
138
+ end
139
+
140
+ send(type: :goaway, last_stream: last_stream,
141
+ error: error, payload: payload)
142
+ @state = :closed
143
+ end
144
+
145
+ # Sends a connection SETTINGS frame to the peer.
146
+ # The values are reflected when the corresponding ACK is received.
147
+ #
148
+ # @param settings [Array or Hash]
149
+ def settings(payload)
150
+ payload = payload.to_a
151
+ connection_error if validate_settings(@local_role, payload)
152
+ @pending_settings << payload
153
+ send(type: :settings, stream: 0, payload: payload)
154
+ @pending_settings << payload
155
+ end
156
+
157
+ # Decodes incoming bytes into HTTP 2.0 frames and routes them to
158
+ # appropriate receivers: connection frames are handled directly, and
159
+ # stream frames are passed to appropriate stream objects.
160
+ #
161
+ # @param data [String] Binary encoded string
162
+ def receive(data)
163
+ @recv_buffer << data
164
+
165
+ # Upon establishment of a TCP connection and determination that
166
+ # HTTP/2.0 will be used by both peers, each endpoint MUST send a
167
+ # connection header as a final confirmation and to establish the
168
+ # initial settings for the HTTP/2.0 connection.
169
+ #
170
+ # Client connection header is 24 byte connection header followed by
171
+ # SETTINGS frame. Server connection header is SETTINGS frame only.
172
+ if @state == :waiting_magic
173
+ if @recv_buffer.size < 24
174
+ if !CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
175
+ fail HandshakeError
176
+ else
177
+ return # maybe next time
178
+ end
179
+ elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
180
+ # MAGIC is OK. Send our settings
181
+ @state = :waiting_connection_preface
182
+ payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
183
+ settings(payload)
184
+ else
185
+ fail HandshakeError
186
+ end
187
+ end
188
+
189
+ while (frame = @framer.parse(@recv_buffer))
190
+ emit(:frame_received, frame)
191
+
192
+ # Header blocks MUST be transmitted as a contiguous sequence of frames
193
+ # with no interleaved frames of any other type, or from any other stream.
194
+ unless @continuation.empty?
195
+ unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
196
+ connection_error
197
+ end
198
+
199
+ @continuation << frame
200
+ return unless frame[:flags].include? :end_headers
201
+
202
+ payload = @continuation.map { |f| f[:payload] }.join
203
+
204
+ frame = @continuation.shift
205
+ @continuation.clear
206
+
207
+ frame.delete(:length)
208
+ frame[:payload] = Buffer.new(payload)
209
+ frame[:flags] << :end_headers
210
+ end
211
+
212
+ # SETTINGS frames always apply to a connection, never a single stream.
213
+ # The stream identifier for a settings frame MUST be zero. If an
214
+ # endpoint receives a SETTINGS frame whose stream identifier field is
215
+ # anything other than 0x0, the endpoint MUST respond with a connection
216
+ # error (Section 5.4.1) of type PROTOCOL_ERROR.
217
+ if connection_frame?(frame)
218
+ connection_management(frame)
219
+ else
220
+ case frame[:type]
221
+ when :headers
222
+ # The last frame in a sequence of HEADERS/CONTINUATION
223
+ # frames MUST have the END_HEADERS flag set.
224
+ unless frame[:flags].include? :end_headers
225
+ @continuation << frame
226
+ return
227
+ end
228
+
229
+ # After sending a GOAWAY frame, the sender can discard frames
230
+ # for new streams. However, any frames that alter connection
231
+ # state cannot be completely ignored. For instance, HEADERS,
232
+ # PUSH_PROMISE and CONTINUATION frames MUST be minimally
233
+ # processed to ensure a consistent compression state
234
+ decode_headers(frame)
235
+ return if @state == :closed
236
+
237
+ stream = @streams[frame[:stream]]
238
+ if stream.nil?
239
+ stream = activate_stream(
240
+ id: frame[:stream],
241
+ weight: frame[:weight] || DEFAULT_WEIGHT,
242
+ dependency: frame[:dependency] || 0,
243
+ exclusive: frame[:exclusive] || false,
244
+ )
245
+ emit(:stream, stream)
246
+ end
247
+
248
+ stream << frame
249
+
250
+ when :push_promise
251
+ # The last frame in a sequence of PUSH_PROMISE/CONTINUATION
252
+ # frames MUST have the END_HEADERS flag set
253
+ unless frame[:flags].include? :end_headers
254
+ @continuation << frame
255
+ return
256
+ end
257
+
258
+ decode_headers(frame)
259
+ return if @state == :closed
260
+
261
+ # PUSH_PROMISE frames MUST be associated with an existing, peer-
262
+ # initiated stream... A receiver MUST treat the receipt of a
263
+ # PUSH_PROMISE on a stream that is neither "open" nor
264
+ # "half-closed (local)" as a connection error (Section 5.4.1) of
265
+ # type PROTOCOL_ERROR. Similarly, a receiver MUST treat the
266
+ # receipt of a PUSH_PROMISE that promises an illegal stream
267
+ # identifier (Section 5.1.1) (that is, an identifier for a stream
268
+ # that is not currently in the "idle" state) as a connection error
269
+ # (Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver
270
+ # recently sent a RST_STREAM frame to cancel the associated stream.
271
+ parent = @streams[frame[:stream]]
272
+ pid = frame[:promise_stream]
273
+
274
+ connection_error(msg: 'missing parent ID') if parent.nil?
275
+
276
+ unless parent.state == :open || parent.state == :half_closed_local
277
+ # An endpoint might receive a PUSH_PROMISE frame after it sends
278
+ # RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
279
+ # The RST_STREAM does not cancel any promised stream. Therefore, if
280
+ # promised streams are not desired, a RST_STREAM can be used to
281
+ # close any of those streams.
282
+ if parent.closed == :local_rst
283
+ # We can either (a) 'resurrect' the parent, or (b) RST_STREAM
284
+ # ... sticking with (b), might need to revisit later.
285
+ send(type: :rst_stream, stream: pid, error: :refused_stream)
286
+ else
287
+ connection_error
288
+ end
289
+ end
290
+
291
+ stream = activate_stream(id: pid, parent: parent)
292
+ emit(:promise, stream)
293
+ stream << frame
294
+ else
295
+ if (stream = @streams[frame[:stream]])
296
+ stream << frame
297
+ else
298
+ # The PRIORITY frame can be sent for a stream in the "idle" or
299
+ # "closed" state. This allows for the reprioritization of a
300
+ # group of dependent streams by altering the priority of an
301
+ # unused or closed parent stream.
302
+ if frame[:type] == :priority
303
+ stream = activate_stream(
304
+ id: frame[:stream],
305
+ weight: frame[:weight] || DEFAULT_WEIGHT,
306
+ dependency: frame[:dependency] || 0,
307
+ exclusive: frame[:exclusive] || false,
308
+ )
309
+
310
+ emit(:stream, stream)
311
+ stream << frame
312
+ else
313
+ # An endpoint that receives an unexpected stream identifier
314
+ # MUST respond with a connection error of type PROTOCOL_ERROR.
315
+ connection_error
316
+ end
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ rescue => e
323
+ raise if e.is_a?(Error::Error)
324
+ connection_error(e: e)
325
+ end
326
+ alias_method :<<, :receive
327
+
328
+ private
329
+
330
+ # Send an outgoing frame. DATA frames are subject to connection flow
331
+ # control and may be split and / or buffered based on current window size.
332
+ # All other frames are sent immediately.
333
+ #
334
+ # @note all frames are currently delivered in FIFO order.
335
+ # @param frame [Hash]
336
+ def send(frame)
337
+ emit(:frame_sent, frame)
338
+ if frame[:type] == :data
339
+ send_data(frame, true)
340
+
341
+ else
342
+ # An endpoint can end a connection at any time. In particular, an
343
+ # endpoint MAY choose to treat a stream error as a connection error.
344
+ if frame[:type] == :rst_stream && frame[:error] == :protocol_error
345
+ goaway(frame[:error])
346
+ else
347
+ # HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
348
+ # RST_STREAM that are not protocol errors
349
+ frames = encode(frame)
350
+ frames.each { |f| emit(:frame, f) }
351
+ end
352
+ end
353
+ end
354
+
355
+ # Applies HTTP 2.0 binary encoding to the frame.
356
+ #
357
+ # @param frame [Hash]
358
+ # @return [Array of Buffer] encoded frame
359
+ def encode(frame)
360
+ frames = []
361
+
362
+ if frame[:type] == :headers || frame[:type] == :push_promise
363
+ frames = encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
364
+ else
365
+ frames = [frame] # otherwise one frame
366
+ end
367
+
368
+ frames.map { |f| @framer.generate(f) }
369
+ end
370
+
371
+ # Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
372
+ # frame addressed to stream ID = 0.
373
+ #
374
+ # @param frame [Hash]
375
+ # @return [Boolean]
376
+ def connection_frame?(frame)
377
+ frame[:stream] == 0 ||
378
+ frame[:type] == :settings ||
379
+ frame[:type] == :ping ||
380
+ frame[:type] == :goaway
381
+ end
382
+
383
+ # Process received connection frame (stream ID = 0).
384
+ # - Handle SETTINGS updates
385
+ # - Connection flow control (WINDOW_UPDATE)
386
+ # - Emit PONG auto-reply to PING frames
387
+ # - Mark connection as closed on GOAWAY
388
+ #
389
+ # @param frame [Hash]
390
+ def connection_management(frame)
391
+ case @state
392
+ when :waiting_connection_preface
393
+ # The first frame MUST be a SETTINGS frame at the start of a connection.
394
+ @state = :connected
395
+ connection_settings(frame)
396
+
397
+ when :connected
398
+ case frame[:type]
399
+ when :settings
400
+ connection_settings(frame)
401
+ when :window_update
402
+ @remote_window += frame[:increment]
403
+ send_data(nil, true)
404
+ when :ping
405
+ if frame[:flags].include? :ack
406
+ emit(:ack, frame[:payload])
407
+ else
408
+ send(type: :ping, stream: 0,
409
+ flags: [:ack], payload: frame[:payload])
410
+ end
411
+ when :goaway
412
+ # Receivers of a GOAWAY frame MUST NOT open additional streams on
413
+ # the connection, although a new connection can be established
414
+ # for new streams.
415
+ @state = :closed
416
+ emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
417
+ when :altsvc, :blocked
418
+ emit(frame[:type], frame)
419
+ else
420
+ connection_error
421
+ end
422
+ else
423
+ connection_error
424
+ end
425
+ end
426
+
427
+ # Validate settings parameters. See sepc Section 6.5.2.
428
+ #
429
+ # @param role [Symbol] The sender's role: :client or :server
430
+ # @return nil if no error. Exception object in case of any error.
431
+ def validate_settings(role, settings)
432
+ settings.each do |key, v|
433
+ case key
434
+ when :settings_header_table_size
435
+ # Any value is valid
436
+ when :settings_enable_push
437
+ case role
438
+ when :server
439
+ # Section 8.2
440
+ # Clients MUST reject any attempt to change the
441
+ # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
442
+ # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
443
+ return ProtocolError.new("invalid #{key} value") unless v == 0
444
+ when :client
445
+ # Any value other than 0 or 1 MUST be treated as a
446
+ # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
447
+ unless v == 0 || v == 1
448
+ return ProtocolError.new("invalid #{key} value")
449
+ end
450
+ end
451
+ when :settings_max_concurrent_streams
452
+ # Any value is valid
453
+ when :settings_initial_window_size
454
+ # Values above the maximum flow control window size of 2^31-1 MUST
455
+ # be treated as a connection error (Section 5.4.1) of type
456
+ # FLOW_CONTROL_ERROR.
457
+ unless v <= 0x7fffffff
458
+ return FlowControlError.new("invalid #{key} value")
459
+ end
460
+ when :settings_max_frame_size
461
+ # The initial value is 2^14 (16,384) octets. The value advertised
462
+ # by an endpoint MUST be between this initial value and the maximum
463
+ # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
464
+ # Values outside this range MUST be treated as a connection error
465
+ # (Section 5.4.1) of type PROTOCOL_ERROR.
466
+ unless 16_384 <= v && v <= 16_777_215
467
+ return ProtocolError.new("invalid #{key} value")
468
+ end
469
+ when :settings_max_header_list_size
470
+ # Any value is valid
471
+ # else # ignore unknown settings
472
+ end
473
+ end
474
+ nil
475
+ end
476
+
477
+ # Update connection settings based on parameters set by the peer.
478
+ #
479
+ # @param frame [Hash]
480
+ def connection_settings(frame)
481
+ connection_error unless frame[:type] == :settings && frame[:stream] == 0
482
+
483
+ # Apply settings.
484
+ # side =
485
+ # local: previously sent and pended our settings should be effective
486
+ # remote: just received peer settings should immediately be effective
487
+ settings, side = if frame[:flags].include?(:ack)
488
+ # Process pending settings we have sent.
489
+ [@pending_settings.shift, :local]
490
+ else
491
+ connection_error(check) if validate_settings(@remote_role, frame[:payload])
492
+ [frame[:payload], :remote]
493
+ end
494
+
495
+ settings.each do |key, v|
496
+ case side
497
+ when :local
498
+ @local_settings[key] = v
499
+ when :remote
500
+ @remote_settings[key] = v
501
+ end
502
+
503
+ case key
504
+ when :settings_max_concurrent_streams
505
+ # Do nothing.
506
+ # The value controls at the next attempt of stream creation.
507
+
508
+ when :settings_initial_window_size
509
+ # A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the available
510
+ # space in a flow control window to become negative. A sender MUST
511
+ # track the negative flow control window, and MUST NOT send new flow
512
+ # controlled frames until it receives WINDOW_UPDATE frames that cause
513
+ # the flow control window to become positive.
514
+ case side
515
+ when :local
516
+ @local_window = @local_window - @local_window_limit + v
517
+ @streams.each do |_id, stream|
518
+ stream.emit(:local_window, stream.local_window - @local_window_limit + v)
519
+ end
520
+
521
+ @local_window_limit = v
522
+ when :remote
523
+ @remote_window = @remote_window - @remote_window_limit + v
524
+ @streams.each do |_id, stream|
525
+ # Event name is :window, not :remote_window
526
+ stream.emit(:window, stream.remote_window - @remote_window_limit + v)
527
+ end
528
+
529
+ @remote_window_limit = v
530
+ end
531
+
532
+ when :settings_header_table_size
533
+ # Setting header table size might cause some headers evicted
534
+ case side
535
+ when :local
536
+ @decompressor.table_size = v
537
+ when :remote
538
+ @compressor.table_size = v
539
+ end
540
+
541
+ when :settings_enable_push
542
+ # nothing to do
543
+
544
+ when :settings_max_frame_size
545
+ # nothing to do
546
+
547
+ # else # ignore unknown settings
548
+ end
549
+ end
550
+
551
+ case side
552
+ when :local
553
+ # Received a settings_ack. Notify application layer.
554
+ emit(:settings_ack, frame, @pending_settings.size)
555
+ when :remote
556
+ unless @state == :closed || @h2c_upgrade == :start
557
+ # Send ack to peer
558
+ send(type: :settings, stream: 0, payload: [], flags: [:ack])
559
+ end
560
+ end
561
+ end
562
+
563
+ # Decode headers payload and update connection decompressor state.
564
+ #
565
+ # The receiver endpoint reassembles the header block by concatenating
566
+ # the individual fragments, then decompresses the block to reconstruct
567
+ # the header set - aka, header payloads are buffered until END_HEADERS,
568
+ # or an END_PROMISE flag is seen.
569
+ #
570
+ # @param frame [Hash]
571
+ def decode_headers(frame)
572
+ if frame[:payload].is_a? String
573
+ frame[:payload] = @decompressor.decode(frame[:payload])
574
+ end
575
+
576
+ rescue => e
577
+ connection_error(:compression_error, e: e)
578
+ end
579
+
580
+ # Encode headers payload and update connection compressor state.
581
+ #
582
+ # @param frame [Hash]
583
+ # @return [Array of Frame]
584
+ def encode_headers(frame)
585
+ payload = frame[:payload]
586
+ payload = @compressor.encode(payload) unless payload.is_a? String
587
+
588
+ frames = []
589
+
590
+ while payload.size > 0
591
+ cont = frame.dup
592
+ cont[:type] = :continuation
593
+ cont[:flags] = []
594
+ cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
595
+ frames << cont
596
+ end
597
+ if frames.empty?
598
+ frames = [frame]
599
+ else
600
+ frames.first[:type] = frame[:type]
601
+ frames.first[:flags] = frame[:flags] - [:end_headers]
602
+ frames.last[:flags] << :end_headers
603
+ end
604
+
605
+ frames
606
+
607
+ rescue => e
608
+ connection_error(:compression_error, e: e)
609
+ nil
610
+ end
611
+
612
+ # Activates new incoming or outgoing stream and registers appropriate
613
+ # connection managemet callbacks.
614
+ #
615
+ # @param id [Integer]
616
+ # @param priority [Integer]
617
+ # @param window [Integer]
618
+ # @param parent [Stream]
619
+ def activate_stream(id: nil, **args)
620
+ connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
621
+
622
+ stream = Stream.new({ connection: self, id: id }.merge(args))
623
+
624
+ # Streams that are in the "open" state, or either of the "half closed"
625
+ # states count toward the maximum number of streams that an endpoint is
626
+ # permitted to open.
627
+ stream.once(:active) { @active_stream_count += 1 }
628
+ stream.once(:close) { @active_stream_count -= 1 }
629
+ stream.on(:promise, &method(:promise)) if self.is_a? Server
630
+ stream.on(:frame, &method(:send))
631
+
632
+ @streams[id] = stream
633
+ end
634
+
635
+ # Emit GOAWAY error indicating to peer that the connection is being
636
+ # aborted, and once sent, raise a local exception.
637
+ #
638
+ # @param error [Symbol]
639
+ # @option error [Symbol] :no_error
640
+ # @option error [Symbol] :internal_error
641
+ # @option error [Symbol] :flow_control_error
642
+ # @option error [Symbol] :stream_closed
643
+ # @option error [Symbol] :frame_too_large
644
+ # @option error [Symbol] :compression_error
645
+ # @param msg [String]
646
+ def connection_error(error = :protocol_error, msg: nil, e: nil)
647
+ goaway(error) unless @state == :closed || @state == :new
648
+
649
+ @state, @error = :closed, error
650
+ klass = error.to_s.split('_').map(&:capitalize).join
651
+ fail Error.const_get(klass), msg || e.message, e.backtrace || []
652
+ end
653
+ end
654
+ end
@@ -0,0 +1,45 @@
1
+ module HTTP2
2
+ # Basic event emitter implementation with support for persistent and
3
+ # one-time event callbacks.
4
+ #
5
+ module Emitter
6
+ # Subscribe to all future events for specified type.
7
+ #
8
+ # @param event [Symbol]
9
+ # @param block [Proc] callback function
10
+ def add_listener(event, &block)
11
+ fail ArgumentError, 'must provide callback' unless block_given?
12
+ listeners(event.to_sym).push block
13
+ end
14
+ alias_method :on, :add_listener
15
+
16
+ # Subscribe to next event (at most once) for specified type.
17
+ #
18
+ # @param event [Symbol]
19
+ # @param block [Proc] callback function
20
+ def once(event, &block)
21
+ add_listener(event) do |*args, &callback|
22
+ block.call(*args, &callback)
23
+ :delete
24
+ end
25
+ end
26
+
27
+ # Emit event with provided arguments.
28
+ #
29
+ # @param event [Symbol]
30
+ # @param args [Array] arguments to be passed to the callbacks
31
+ # @param block [Proc] callback function
32
+ def emit(event, *args, &block)
33
+ listeners(event).delete_if do |cb|
34
+ cb.call(*args, &block) == :delete
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def listeners(event)
41
+ @listeners ||= Hash.new { |hash, key| hash[key] = [] }
42
+ @listeners[event]
43
+ end
44
+ end
45
+ end