http-2 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEAoxV61qrUWRDDjv6OgMRm4A2DIIP+7sQhUCTOWeguCCOg/Ffe
3
+ BQ8gnfidCzitoQfkFHRaqQNUvekVtjLyMHb0bdN0STIG33FBdPsOGSLb9sa1vcGg
4
+ nwpi0kQ2YHEgE2nBBervadlbPqOCf3B2nIq0sI8uPpveAoJMbVBRrRnCWhagGegn
5
+ Y+Vr0HSUogCN8d4DQv1BgPuXS7KfPr8Fsfv0zHOpwwZqEVtxh3hvzQKUEodNcN8A
6
+ 79BZlGhUJQOVj8mvybPgsrmIdZol20CRIw5xhmiY56MkQEKT65O9iY/m53zrvZMa
7
+ c5LiyTrG2qH0RZDPvwK+VPm0M7lNBK0PCIfDDwIDAQABAoIBAQCZShphZsccRJ6c
8
+ bPdDX9iW5vyG9qsMgPwTGdWAOrXx3pN2PZ0pwjNVaRcsMgU6JHGlLEz/Kmtf6pQG
9
+ 41I0bcuI48Yc+tHs+sadD1IMHHEHP3Yau8KfWyLSI129Pvf4Z2IQjuik5LJYaVbD
10
+ NNG4iMQYZS0Bmn6Oey0dXu62t0ywYa0qvbIDse/RmjTQSTipuvGg8/QEAeRGABv8
11
+ Nd4Esya0zuxk6hGaNp3hkjyRkeoC7RsBVJbFSnp6gSubPdXwrJyHfySKe9jvrDG3
12
+ Q/AzyHUh/6EODd5n66x0p6rq7oo9/PnLvZJY8jIGWG+aEp68RJyEgimrwll0rAWw
13
+ /buqijGRAoGBANimL8407fFirmct7BceavaeJfXPK5yWiOhVX0XlJ0phAFuaAxK3
14
+ 5HVT7DD+KKV66g1jtS9FUVZGDiYFHlsdsYuHVYcRmr0h5rZr941obrDwNrM9Nf9C
15
+ 0uehN5+n/FaeGoQLR3V4THoP3rlkYTlLpQnI5mKA19JukXnIiJM9ARUZAoGBAMC0
16
+ mcVsVuSKSFwURtQHHIufxL6SqC2kLTwIQ7exqejNYPCqCiif+ZWOmsTqbVGAGbMK
17
+ Ohak4oLwN5IGCl4jNQG+vWagREkx6OXSk5NYcfoNBrOm+0UoFRzoEA85s7Dy6PuD
18
+ tBucNZpt1sGauzkCSx7C8jj4ZlSwkv0XhBFfbTZnAoGBAK2wBjF+U6iq4YFM2rLq
19
+ KvzOa0Z3MdKXCOmiz//cKDTEMaI+heoyzZCWmIvqpzGLqirT3gUowH23Kk6m2eBY
20
+ nOdst0/S+Eha7nkfc9bFe8CUxHXMRAcCTs1ufYadCXtzw3RLCp4NtNpC8N+Wry9d
21
+ CtIeYz1jaCOHi0+kSoIobT65AoGAc6hxWkJp7ITqZQlucTdLdKmRheeztKEC3TMA
22
+ obGqDqWldww3SKarP431ahZhQjcmNYT/1DNmF7xhPe0OL+3llISMXJn4Ig4ogDdg
23
+ h2DgF3nV+eFQkfM6qLzHVrwFE0DXgI1NffzFV0hxSoW5tL+honbStkqv8EiCEBEb
24
+ HOovPCUCgYBpXuPARd2ycInAulVHijJmj2rmK7f41ZhVCWovYjcCWyeJyLIO7j+b
25
+ MBJZbmwpStJhEjW64nE2zZGWg2HCBbvZz5/SXIr3fp7qVXwpn1TvB/TJDf43t0oF
26
+ 3caLgyQYoQCsVHKT3cU4s3wuog/DyHKh9FtRkcJrEy7h9Rrc+ModbA==
27
+ -----END RSA PRIVATE KEY-----
@@ -11,6 +11,10 @@ OptionParser.new do |opts|
11
11
  opts.on('-p', '--port [Integer]', 'listen port') do |v|
12
12
  options[:port] = v
13
13
  end
14
+
15
+ opts.on('-u', '--push', 'Push message') do |_v|
16
+ options[:push] = true
17
+ end
14
18
  end.parse!
15
19
 
16
20
  puts "Starting server on port #{options[:port]}"
@@ -18,9 +22,23 @@ server = TCPServer.new(options[:port])
18
22
 
19
23
  if options[:secure]
20
24
  ctx = OpenSSL::SSL::SSLContext.new
21
- ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/mycert.pem'))
22
- ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/mykey.pem'))
23
- ctx.npn_protocols = [DRAFT]
25
+ ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
26
+ ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
27
+
28
+ ctx.ssl_version = :TLSv1_2
29
+ ctx.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
30
+ ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]
31
+
32
+ ctx.alpn_protocols = ['h2']
33
+
34
+ ctx.alpn_select_cb = lambda do |protocols|
35
+ raise "Protocol #{DRAFT} is required" if protocols.index(DRAFT).nil?
36
+ DRAFT
37
+ end
38
+
39
+ ctx.tmp_ecdh_callback = lambda do |*_args|
40
+ OpenSSL::PKey::EC.new 'prime256v1'
41
+ end
24
42
 
25
43
  server = OpenSSL::SSL::SSLServer.new(server, ctx)
26
44
  end
@@ -32,7 +50,7 @@ loop do
32
50
  conn = HTTP2::Server.new
33
51
  conn.on(:frame) do |bytes|
34
52
  # puts "Writing bytes: #{bytes.unpack("H*").first}"
35
- sock.write bytes
53
+ sock.is_a?(TCPSocket) ? sock.sendmsg(bytes) : sock.write(bytes)
36
54
  end
37
55
  conn.on(:frame_sent) do |frame|
38
56
  puts "Sent frame: #{frame.inspect}"
@@ -76,9 +94,35 @@ loop do
76
94
  'content-type' => 'text/plain',
77
95
  }, end_stream: false)
78
96
 
97
+ if options[:push]
98
+ push_streams = []
99
+
100
+ # send 10 promises
101
+ 10.times do |i|
102
+ puts 'sending push'
103
+
104
+ head = { ':method' => 'GET',
105
+ ':authority' => 'localhost',
106
+ ':scheme' => 'https',
107
+ ':path' => "/other_resource/#{i}" }
108
+
109
+ stream.promise(head) do |push|
110
+ push.headers(':status' => '200', 'content-type' => 'text/plain', 'content-length' => '11')
111
+ push_streams << push
112
+ end
113
+ end
114
+ end
115
+
79
116
  # split response into multiple DATA frames
80
117
  stream.data(response.slice!(0, 5), end_stream: false)
81
118
  stream.data(response)
119
+
120
+ if options[:push]
121
+ push_streams.each_with_index do |push, i|
122
+ sleep 1
123
+ push.data("push_data #{i}")
124
+ end
125
+ end
82
126
  end
83
127
  end
84
128
 
@@ -1,6 +1,7 @@
1
+ # frozen_string_literals: true
2
+
1
3
  require_relative 'helper'
2
4
  require 'http_parser'
3
- require 'base64'
4
5
 
5
6
  options = { port: 8080 }
6
7
  OptionParser.new do |opts|
@@ -20,15 +21,24 @@ server = TCPServer.new(options[:port])
20
21
 
21
22
  if options[:secure]
22
23
  ctx = OpenSSL::SSL::SSLContext.new
23
- ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/mycert.pem'))
24
- ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/mykey.pem'))
24
+ ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
25
+ ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
25
26
  ctx.npn_protocols = [DRAFT]
26
27
 
27
28
  server = OpenSSL::SSL::SSLServer.new(server, ctx)
28
29
  end
29
30
 
31
+ def request_header_hash
32
+ Hash.new do |hash, key|
33
+ k = key.to_s.downcase
34
+ k.tr! '_', '-'
35
+ _, value = hash.find { |header_key, _| header_key.downcase == k }
36
+ hash[key] = value if value
37
+ end
38
+ end
39
+
30
40
  class UpgradeHandler
31
- VALID_UPGRADE_METHODS = %w(GET OPTIONS)
41
+ VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
32
42
  UPGRADE_RESPONSE = <<-RESP
33
43
  HTTP/1.1 101 Switching Protocols
34
44
  Connection: Upgrade
@@ -41,6 +51,7 @@ RESP
41
51
  def initialize(conn, sock)
42
52
  @conn, @sock = conn, sock
43
53
  @complete, @parsing = false, false
54
+ @headers = request_header_hash
44
55
  @body = ''
45
56
  @parser = ::HTTP::Parser.new(self)
46
57
  end
@@ -68,7 +79,7 @@ RESP
68
79
  end
69
80
 
70
81
  def on_headers_complete(headers)
71
- @headers = headers
82
+ @headers.merge! headers
72
83
  end
73
84
 
74
85
  def on_body(chunk)
@@ -100,7 +111,8 @@ loop do
100
111
 
101
112
  conn.on(:stream) do |stream|
102
113
  log = Logger.new(stream.id)
103
- req, buffer = {}, ''
114
+ req = request_header_hash
115
+ buffer = ''
104
116
 
105
117
  stream.on(:active) { log.info 'client opened new stream' }
106
118
  stream.on(:close) do
@@ -108,7 +120,7 @@ loop do
108
120
  end
109
121
 
110
122
  stream.on(:headers) do |h|
111
- req = Hash[*h.flatten]
123
+ req.merge! Hash[*h.flatten]
112
124
  log.info "request headers: #{h}"
113
125
  end
114
126
 
@@ -122,9 +134,7 @@ loop do
122
134
 
123
135
  if req['Upgrade']
124
136
  log.info "Processing h2c Upgrade request: #{req}"
125
-
126
- # Don't respond to OPTIONS...
127
- if req[':method'] != 'OPTIONS'
137
+ if req[':method'] != 'OPTIONS' # Don't respond to OPTIONS...
128
138
  response = 'Hello h2c world!'
129
139
  stream.headers({
130
140
  ':status' => '200',
@@ -1,35 +1,61 @@
1
+ require 'forwardable'
2
+
1
3
  module HTTP2
2
- # Simple binary buffer backed by string.
3
- #
4
- # TODO: Refactor, it would be better if Buffer were not a String subclass,
5
- # but rather wrap a string and only expose the mutating API needed so that
6
- # the possible surface for things to go wrong stays controllable.
7
- # - https://github.com/igrigorik/http-2/pull/46
4
+ # Binary buffer wraps String.
8
5
  #
9
- class Buffer < String
6
+ class Buffer
7
+ extend Forwardable
8
+
9
+ def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
10
+ :size, :each_byte, :to_str, :to_s, :length, :inspect,
11
+ :[], :[]=, :empty?, :bytesize, :include?
12
+
10
13
  UINT32 = 'N'.freeze
11
14
  private_constant :UINT32
12
15
 
13
16
  # Forces binary encoding on the string
14
- def initialize(*)
15
- super.force_encoding(Encoding::BINARY)
17
+ def initialize(str = '')
18
+ str = str.dup if str.frozen?
19
+ @buffer = str.force_encoding(Encoding::BINARY)
16
20
  end
17
21
 
18
22
  # Emulate StringIO#read: slice first n bytes from the buffer.
19
23
  #
20
24
  # @param n [Integer] number of bytes to slice from the buffer
21
25
  def read(n)
22
- Buffer.new(slice!(0, n))
26
+ Buffer.new(@buffer.slice!(0, n))
23
27
  end
24
28
 
25
- # Alias getbyte to readbyte
26
- alias_method :readbyte, :getbyte
27
-
28
29
  # Emulate StringIO#getbyte: slice first byte from buffer.
29
30
  def getbyte
30
31
  read(1).ord
31
32
  end
32
33
 
34
+ def slice!(*args)
35
+ Buffer.new(@buffer.slice!(*args))
36
+ end
37
+
38
+ def slice(*args)
39
+ Buffer.new(@buffer.slice(*args))
40
+ end
41
+
42
+ def force_encoding(*args)
43
+ @buffer = @buffer.force_encoding(*args)
44
+ end
45
+
46
+ def ==(other)
47
+ @buffer == other
48
+ end
49
+
50
+ def +(other)
51
+ @buffer += other
52
+ end
53
+
54
+ # Emulate String#getbyte: return nth byte from buffer.
55
+ def readbyte(n)
56
+ @buffer[n].ord
57
+ end
58
+
33
59
  # Slice unsigned 32-bit integer from buffer.
34
60
  # @return [Integer]
35
61
  def read_uint32
@@ -41,7 +67,9 @@ module HTTP2
41
67
  [:<<, :prepend].each do |mutating_method|
42
68
  define_method(mutating_method) do |string|
43
69
  string = string.dup if string.frozen?
44
- super(string.force_encoding(Encoding::BINARY))
70
+ @buffer.send mutating_method, string.force_encoding(Encoding::BINARY)
71
+
72
+ self
45
73
  end
46
74
  end
47
75
  end
@@ -527,12 +527,12 @@ module HTTP2
527
527
 
528
528
  case header[:type]
529
529
  when :indexed
530
- fail CompressionError if header[:name] == 0
530
+ fail CompressionError if (header[:name]).zero?
531
531
  header[:name] -= 1
532
532
  when :changetablesize
533
533
  header[:value] = header[:name]
534
534
  else
535
- if header[:name] == 0
535
+ if (header[:name]).zero?
536
536
  header[:name] = string(buf)
537
537
  else
538
538
  header[:name] -= 1
@@ -56,7 +56,7 @@ module HTTP2
56
56
  # infinity, but is automatically updated on receipt of peer settings).
57
57
  attr_reader :local_window
58
58
  attr_reader :remote_window
59
- alias_method :window, :local_window
59
+ alias window local_window
60
60
 
61
61
  # Current settings value for local and peer
62
62
  attr_reader :local_settings
@@ -81,6 +81,7 @@ module HTTP2
81
81
 
82
82
  @active_stream_count = 0
83
83
  @streams = {}
84
+ @streams_recently_closed = {}
84
85
  @pending_settings = []
85
86
 
86
87
  @framer = Framer.new
@@ -226,6 +227,10 @@ module HTTP2
226
227
  else
227
228
  case frame[:type]
228
229
  when :headers
230
+ # When server receives even-numbered stream identifier,
231
+ # the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
232
+ connection_error if frame[:stream].even? && self.is_a?(Server)
233
+
229
234
  # The last frame in a sequence of HEADERS/CONTINUATION
230
235
  # frames MUST have the END_HEADERS flag set.
231
236
  unless frame[:flags].include? :end_headers
@@ -278,6 +283,12 @@ module HTTP2
278
283
  parent = @streams[frame[:stream]]
279
284
  pid = frame[:promise_stream]
280
285
 
286
+ # if PUSH parent is recently closed, RST_STREAM the push
287
+ if @streams_recently_closed[frame[:stream]]
288
+ send(type: :rst_stream, stream: pid, error: :refused_stream)
289
+ return
290
+ end
291
+
281
292
  connection_error(msg: 'missing parent ID') if parent.nil?
282
293
 
283
294
  unless parent.state == :open || parent.state == :half_closed_local
@@ -330,7 +341,7 @@ module HTTP2
330
341
  raise if e.is_a?(Error::Error)
331
342
  connection_error(e: e)
332
343
  end
333
- alias_method :<<, :receive
344
+ alias << receive
334
345
 
335
346
  private
336
347
 
@@ -364,12 +375,10 @@ module HTTP2
364
375
  # @param frame [Hash]
365
376
  # @return [Array of Buffer] encoded frame
366
377
  def encode(frame)
367
- frames = []
368
-
369
- if frame[:type] == :headers || frame[:type] == :push_promise
370
- frames = encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
378
+ frames = if frame[:type] == :headers || frame[:type] == :push_promise
379
+ encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
371
380
  else
372
- frames = [frame] # otherwise one frame
381
+ [frame] # otherwise one frame
373
382
  end
374
383
 
375
384
  frames.map { |f| @framer.generate(f) }
@@ -381,7 +390,7 @@ module HTTP2
381
390
  # @param frame [Hash]
382
391
  # @return [Boolean]
383
392
  def connection_frame?(frame)
384
- frame[:stream] == 0 ||
393
+ (frame[:stream]).zero? ||
385
394
  frame[:type] == :settings ||
386
395
  frame[:type] == :ping ||
387
396
  frame[:type] == :goaway
@@ -447,11 +456,11 @@ module HTTP2
447
456
  # Clients MUST reject any attempt to change the
448
457
  # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
449
458
  # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
450
- return ProtocolError.new("invalid #{key} value") unless v == 0
459
+ return ProtocolError.new("invalid #{key} value") unless v.zero?
451
460
  when :client
452
461
  # Any value other than 0 or 1 MUST be treated as a
453
462
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
454
- unless v == 0 || v == 1
463
+ unless v.zero? || v == 1
455
464
  return ProtocolError.new("invalid #{key} value")
456
465
  end
457
466
  end
@@ -485,7 +494,7 @@ module HTTP2
485
494
  #
486
495
  # @param frame [Hash]
487
496
  def connection_settings(frame)
488
- connection_error unless frame[:type] == :settings && frame[:stream] == 0
497
+ connection_error unless frame[:type] == :settings && (frame[:stream]).zero?
489
498
 
490
499
  # Apply settings.
491
500
  # side =
@@ -576,7 +585,7 @@ module HTTP2
576
585
  #
577
586
  # @param frame [Hash]
578
587
  def decode_headers(frame)
579
- if frame[:payload].is_a? String
588
+ if frame[:payload].is_a? Buffer
580
589
  frame[:payload] = @decompressor.decode(frame[:payload])
581
590
  end
582
591
 
@@ -590,11 +599,11 @@ module HTTP2
590
599
  # @return [Array of Frame]
591
600
  def encode_headers(frame)
592
601
  payload = frame[:payload]
593
- payload = @compressor.encode(payload) unless payload.is_a? String
602
+ payload = @compressor.encode(payload) unless payload.is_a? Buffer
594
603
 
595
604
  frames = []
596
605
 
597
- while payload.size > 0
606
+ while payload.bytesize > 0
598
607
  cont = frame.dup
599
608
  cont[:type] = :continuation
600
609
  cont[:flags] = []
@@ -632,7 +641,18 @@ module HTTP2
632
641
  # states count toward the maximum number of streams that an endpoint is
633
642
  # permitted to open.
634
643
  stream.once(:active) { @active_stream_count += 1 }
635
- stream.once(:close) { @active_stream_count -= 1 }
644
+ stream.once(:close) do
645
+ @streams.delete id
646
+ @active_stream_count -= 1
647
+
648
+ # Store a reference to the closed stream, such that we can respond
649
+ # to any in-flight frames while close is registered on both sides.
650
+ # References to such streams will be purged whenever another stream
651
+ # is closed, with a minimum of 15s RTT time window.
652
+ @streams_recently_closed.delete_if { |_, v| (Time.now - v) > 15 }
653
+ @streams_recently_closed[id] = Time.now
654
+ end
655
+
636
656
  stream.on(:promise, &method(:promise)) if self.is_a? Server
637
657
  stream.on(:frame, &method(:send))
638
658
  stream.on(:window_update, &method(:window_update))
@@ -656,7 +676,9 @@ module HTTP2
656
676
 
657
677
  @state, @error = :closed, error
658
678
  klass = error.to_s.split('_').map(&:capitalize).join
659
- fail Error.const_get(klass), msg || e.message, e.backtrace || []
679
+ msg ||= e && e.message
680
+ backtrace = (e && e.backtrace) || []
681
+ fail Error.const_get(klass), msg, backtrace
660
682
  end
661
683
  end
662
684
  end
@@ -11,7 +11,7 @@ module HTTP2
11
11
  fail ArgumentError, 'must provide callback' unless block_given?
12
12
  listeners(event.to_sym).push block
13
13
  end
14
- alias_method :on, :add_listener
14
+ alias on add_listener
15
15
 
16
16
  # Subscribe to next event (at most once) for specified type.
17
17
  #