plum 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/plum/stream.rb CHANGED
@@ -9,25 +9,21 @@ module Plum
9
9
  attr_reader :id, :state, :connection
10
10
  attr_reader :weight, :exclusive
11
11
  attr_accessor :parent
12
+ # The child (depending on this stream) streams.
13
+ attr_reader :children
12
14
 
13
15
  def initialize(con, id, state: :idle, weight: 16, parent: nil, exclusive: false)
14
16
  @connection = con
15
17
  @id = id
16
18
  @state = state
17
19
  @continuation = []
20
+ @children = Set.new
18
21
 
19
22
  initialize_flow_control(send: @connection.remote_settings[:initial_window_size],
20
23
  recv: @connection.local_settings[:initial_window_size])
21
24
  update_dependency(weight: weight, parent: parent, exclusive: exclusive)
22
25
  end
23
26
 
24
- # Returns the child (depending on this stream) streams.
25
- #
26
- # @return [Array<Stream>] The child streams.
27
- def children
28
- @connection.streams.values.select {|c| c.parent == self }.freeze
29
- end
30
-
31
27
  # Processes received frames for this stream. Internal use.
32
28
  # @private
33
29
  def receive_frame(frame)
@@ -58,7 +54,6 @@ module Plum
58
54
  end
59
55
 
60
56
  # Closes this stream. Sends RST_STREAM frame to the peer.
61
- #
62
57
  # @param error_type [Symbol] The error type to be contained in the RST_STREAM frame.
63
58
  def close(error_type = :no_error)
64
59
  @state = :closed
@@ -67,20 +62,30 @@ module Plum
67
62
 
68
63
  private
69
64
  def send_immediately(frame)
70
- callback(:send_frame, frame)
71
65
  @connection.send(frame)
72
66
  end
73
67
 
74
68
  def update_dependency(weight: nil, parent: nil, exclusive: nil)
75
69
  raise StreamError.new(:protocol_error, "A stream cannot depend on itself.") if parent == self
76
- @weight = weight unless weight.nil?
77
- @parent = parent unless parent.nil?
78
- @exclusive = exclusive unless exclusive.nil?
79
-
80
- if exclusive == true
81
- parent.children.each do |child|
82
- next if child == self
83
- child.parent = self
70
+
71
+ if weight
72
+ @weight = weight
73
+ end
74
+
75
+ if parent
76
+ @parent = parent
77
+ @parent.children << self
78
+ end
79
+
80
+ if exclusive != nil
81
+ @exclusive = exclusive
82
+ if @parent && exclusive
83
+ @parent.children.to_a.each do |child|
84
+ next if child == self
85
+ @parent.children.delete(child)
86
+ child.parent = self
87
+ @children << child
88
+ end
84
89
  end
85
90
  end
86
91
  end
@@ -105,7 +110,7 @@ module Plum
105
110
  raise StreamError.new(:stream_closed)
106
111
  end
107
112
 
108
- if frame.flags.include?(:padded)
113
+ if frame.padded?
109
114
  padding_length = frame.payload.uint8(0)
110
115
  if padding_length >= frame.length
111
116
  raise ConnectionError.new(:protocol_error, "padding is too long")
@@ -116,18 +121,17 @@ module Plum
116
121
  end
117
122
  callback(:data, body)
118
123
 
119
- receive_end_stream if frame.flags.include?(:end_stream)
124
+ receive_end_stream if frame.end_stream?
120
125
  end
121
126
 
122
127
  def receive_complete_headers(frames)
123
- frames = frames.dup
124
128
  first = frames.shift
125
129
 
126
130
  payload = first.payload
127
131
  first_length = first.length
128
132
  padding_length = 0
129
133
 
130
- if first.flags.include?(:padded)
134
+ if first.padded?
131
135
  padding_length = payload.uint8
132
136
  first_length -= 1 + padding_length
133
137
  payload = payload.byteslice(1, first_length)
@@ -135,7 +139,7 @@ module Plum
135
139
  payload = payload.dup
136
140
  end
137
141
 
138
- if first.flags.include?(:priority)
142
+ if first.priority?
139
143
  receive_priority_payload(payload.byteshift(5))
140
144
  first_length -= 5
141
145
  end
@@ -156,7 +160,7 @@ module Plum
156
160
 
157
161
  callback(:headers, decoded_headers)
158
162
 
159
- receive_end_stream if first.flags.include?(:end_stream)
163
+ receive_end_stream if first.end_stream?
160
164
  end
161
165
 
162
166
  def receive_headers(frame)
@@ -171,7 +175,7 @@ module Plum
171
175
  @state = :open
172
176
  callback(:open)
173
177
 
174
- if frame.flags.include?(:end_headers)
178
+ if frame.end_headers?
175
179
  receive_complete_headers([frame])
176
180
  else
177
181
  @continuation << frame
@@ -182,7 +186,7 @@ module Plum
182
186
  # state error mustn't happen: server_connection validates
183
187
  @continuation << frame
184
188
 
185
- if frame.flags.include?(:end_headers)
189
+ if frame.end_headers?
186
190
  receive_complete_headers(@continuation)
187
191
  @continuation.clear
188
192
  end
@@ -214,5 +218,11 @@ module Plum
214
218
  callback(:rst_stream, frame)
215
219
  @state = :closed # MUST NOT send RST_STREAM
216
220
  end
221
+
222
+ # override EventEmitter
223
+ def callback(name, *args)
224
+ super(name, *args)
225
+ @connection.callback(name, self, *args)
226
+ end
217
227
  end
218
228
  end
@@ -3,7 +3,6 @@ using Plum::BinaryString
3
3
  module Plum
4
4
  module StreamUtils
5
5
  # Responds to a HTTP request.
6
- #
7
6
  # @param headers [Enumerable<String, String>] The response headers.
8
7
  # @param body [String, IO] The response body.
9
8
  def respond(headers, body = nil, end_stream: true) # TODO: priority, padding
@@ -16,7 +15,6 @@ module Plum
16
15
  end
17
16
 
18
17
  # Reserves a stream to server push. Sends PUSH_PROMISE and create new stream.
19
- #
20
18
  # @param headers [Enumerable<String, String>] The *request* headers. It must contain all of them: ':authority', ':method', ':scheme' and ':path'.
21
19
  # @return [Stream] The stream to send push response.
22
20
  def promise(headers)
@@ -30,7 +28,6 @@ module Plum
30
28
  end
31
29
 
32
30
  # Sends response headers. If the encoded frame is larger than MAX_FRAME_SIZE, the headers will be splitted into HEADERS frame and CONTINUATION frame(s).
33
- #
34
31
  # @param headers [Enumerable<String, String>] The response headers.
35
32
  # @param end_stream [Boolean] Set END_STREAM flag or not.
36
33
  def send_headers(headers, end_stream:)
@@ -44,7 +41,6 @@ module Plum
44
41
  end
45
42
 
46
43
  # Sends DATA frame. If the data is larger than MAX_FRAME_SIZE, DATA frame will be splitted.
47
- #
48
44
  # @param data [String, IO] The data to send.
49
45
  # @param end_stream [Boolean] Set END_STREAM flag or not.
50
46
  def send_data(data, end_stream: true)
data/lib/plum/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Plum
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/plum.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "openssl"
2
2
  require "socket"
3
3
  require "base64"
4
+ require "set"
4
5
  require "plum/version"
5
6
  require "plum/errors"
6
7
  require "plum/binary_string"
@@ -0,0 +1,49 @@
1
+ module Rack
2
+ module Handler
3
+ class Plum
4
+ def self.run(app, options = {})
5
+ opts = default_options.merge(options)
6
+
7
+ config = ::Plum::Rack::Config.new(
8
+ listeners: [
9
+ {
10
+ listener: ::Plum::Rack::TLSListener,
11
+ hostname: opts[:Host],
12
+ port: opts[:Port].to_i
13
+ }
14
+ ],
15
+ debug: !!opts[:Debug]
16
+ )
17
+
18
+ @server = ::Plum::Rack::Server.new(app, config)
19
+ yield @server if block_given? # TODO
20
+ @server.start
21
+ end
22
+
23
+ def self.shutdown
24
+ @server.stop if @server
25
+ end
26
+
27
+ def self.valid_options
28
+ {
29
+ "Host=HOST" => "Hostname to listen on (default: #{default_options[:Host]})",
30
+ "Port=PORT" => "Port to listen on (default: #{default_options[:Port]})",
31
+ "Debug" => "Turn on debug mode (default: #{default_options[:Debug]})",
32
+ }
33
+ end
34
+
35
+ private
36
+ def self.default_options
37
+ rack_env = ENV["RACK_ENV"] || "development"
38
+ dev = rack_env == "development"
39
+ default_options = {
40
+ Host: dev ? "localhost" : "0.0.0.0",
41
+ Port: 8080,
42
+ Debug: dev,
43
+ }
44
+ end
45
+ end
46
+
47
+ register(:plum, ::Rack::Handler::Plum)
48
+ end
49
+ end
data/plum.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.10"
22
22
  spec.add_development_dependency "http_parser.rb"
23
+ spec.add_development_dependency "rack"
23
24
  spec.add_development_dependency "rake"
24
25
  spec.add_development_dependency "yard"
25
26
  spec.add_development_dependency "minitest", "~> 5.7.0"
@@ -4,38 +4,38 @@ class HPACKDecoderTest < Minitest::Test
4
4
  # C.1.1
5
5
  def test_hpack_read_integer_small
6
6
  buf = [0b11001010, 0b00001111].pack("C*")
7
- result = new_decoder.__send__(:read_integer!, buf, 5)
7
+ result, succ = new_decoder.__send__(:read_integer, buf, 0, 5)
8
8
  assert_equal(10, result)
9
- assert_equal([0b00001111].pack("C*"), buf)
9
+ assert_equal(1, succ)
10
10
  end
11
11
 
12
12
  # C.1.2
13
13
  def test_hpack_read_integer_big
14
14
  buf = [0b11011111, 0b10011010, 0b00001010, 0b00001111].pack("C*")
15
- result = new_decoder.__send__(:read_integer!, buf, 5)
15
+ result, succ = new_decoder.__send__(:read_integer, buf, 0, 5)
16
16
  assert_equal(1337, result)
17
- assert_equal([0b00001111].pack("C*"), buf)
17
+ assert_equal(3, succ)
18
18
  end
19
19
 
20
20
  # C.1.3
21
21
  def test_hpack_read_integer_8prefix
22
22
  buf = [0b00101010, 0b00001111].pack("C*")
23
- result = new_decoder.__send__(:read_integer!, buf, 8)
23
+ result, succ = new_decoder.__send__(:read_integer, buf, 0, 8)
24
24
  assert_equal(42, result)
25
- assert_equal([0b00001111].pack("C*"), buf)
25
+ assert_equal(1, succ)
26
26
  end
27
27
 
28
28
  def test_hpack_read_integer_too_big
29
29
  buf = [0b11011111, 0b10011010, 0b10001010, 0b10001111, 0b11111111, 0b00000011].pack("C*")
30
30
  assert_raises(HPACKError) {
31
- new_decoder.__send__(:read_integer!, buf, 5)
31
+ new_decoder.__send__(:read_integer, buf, 0, 5)
32
32
  }
33
33
  end
34
34
 
35
35
  def test_hpack_read_integer_incomplete
36
36
  buf = [0b11011111, 0b10011010].pack("C*")
37
37
  assert_raises(HPACKError) {
38
- new_decoder.__send__(:read_integer!, buf, 5)
38
+ new_decoder.__send__(:read_integer, buf, 0, 5)
39
39
  }
40
40
  end
41
41
 
@@ -3,19 +3,19 @@ require "test_helper"
3
3
  class HPACKEncoderTest < Minitest::Test
4
4
  # C.1.1
5
5
  def test_hpack_encode_integer_small
6
- result = new_encoder(1 << 31).__send__(:encode_integer, 10, 5)
6
+ result = new_encoder(1 << 31).__send__(:encode_integer, 10, 5, 0b00000000)
7
7
  assert_equal([0b00001010].pack("C*"), result)
8
8
  end
9
9
 
10
10
  # C.1.2
11
11
  def test_hpack_encode_integer_big
12
- result = new_encoder(1 << 31).__send__(:encode_integer, 1337, 5)
12
+ result = new_encoder(1 << 31).__send__(:encode_integer, 1337, 5, 0b000000)
13
13
  assert_equal([0b00011111, 0b10011010, 0b00001010].pack("C*"), result)
14
14
  end
15
15
 
16
16
  # C.1.3
17
17
  def test_hpack_encode_integer_8prefix
18
- result = new_encoder(1 << 31).__send__(:encode_integer, 42, 8)
18
+ result = new_encoder(1 << 31).__send__(:encode_integer, 42, 8, 0b000000)
19
19
  assert_equal([0b00101010].pack("C*"), result)
20
20
  end
21
21
 
@@ -8,7 +8,7 @@ class StreamHandleFrameTest < Minitest::Test
8
8
  payload = "ABC" * 5
9
9
  open_new_stream(state: :open) {|stream|
10
10
  data = nil
11
- stream.on(:data) {|_data| data = _data }
11
+ stream.connection.on(:data) {|_, _data| data = _data }
12
12
  stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
13
13
  flags: [], payload: payload))
14
14
  assert_equal(payload, data)
@@ -19,7 +19,7 @@ class StreamHandleFrameTest < Minitest::Test
19
19
  payload = "ABC" * 5
20
20
  open_new_stream(state: :open) {|stream|
21
21
  data = nil
22
- stream.on(:data) {|_data| data = _data }
22
+ stream.connection.on(:data) {|_, _data| data = _data }
23
23
  stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
24
24
  flags: [:padded], payload: "".push_uint8(6).push(payload).push("\x00"*6)))
25
25
  assert_equal(payload, data)
@@ -59,7 +59,7 @@ class StreamHandleFrameTest < Minitest::Test
59
59
  def test_stream_handle_headers_single
60
60
  open_new_stream {|stream|
61
61
  headers = nil
62
- stream.on(:headers) {|_headers|
62
+ stream.connection.on(:headers) {|_, _headers|
63
63
  headers = _headers
64
64
  }
65
65
  stream.receive_frame(Frame.new(type: :headers,
@@ -75,7 +75,7 @@ class StreamHandleFrameTest < Minitest::Test
75
75
  open_new_stream {|stream|
76
76
  payload = HPACK::Encoder.new(0).encode([[":path", "/"]])
77
77
  headers = nil
78
- stream.on(:headers) {|_headers|
78
+ stream.connection.on(:headers) {|_, _headers|
79
79
  headers = _headers
80
80
  }
81
81
  stream.receive_frame(Frame.new(type: :headers,
@@ -96,7 +96,7 @@ class StreamHandleFrameTest < Minitest::Test
96
96
  open_new_stream {|stream|
97
97
  payload = HPACK::Encoder.new(0).encode([[":path", "/"]])
98
98
  headers = nil
99
- stream.on(:headers) {|_headers|
99
+ stream.connection.on(:headers) {|_, _headers|
100
100
  headers = _headers
101
101
  }
102
102
  stream.receive_frame(Frame.new(type: :headers,
@@ -156,7 +156,7 @@ class StreamHandleFrameTest < Minitest::Test
156
156
  stream = open_new_stream(con)
157
157
 
158
158
  headers = nil
159
- stream.on(:headers) {|_headers| headers = _headers }
159
+ stream.connection.on(:headers) {|_, _headers| headers = _headers }
160
160
  header_block = HPACK::Encoder.new(0).encode([[":path", "/"]])
161
161
  payload = "".push_uint32((1 << 31) | parent.id)
162
162
  .push_uint8(50)
@@ -4,6 +4,7 @@ class FrameTest < Minitest::Test
4
4
  # Frame.parse!
5
5
  def test_parse_header_uncomplete
6
6
  buffer = "\x00\x00\x00" << "\x00" << "\x00"
7
+ buffer.force_encoding(Encoding::BINARY)
7
8
  buffer_orig = buffer.dup
8
9
  assert_nil(Plum::Frame.parse!(buffer))
9
10
  assert_equal(buffer_orig, buffer)
@@ -11,6 +12,7 @@ class FrameTest < Minitest::Test
11
12
 
12
13
  def test_parse_body_uncomplete
13
14
  buffer = "\x00\x00\x03" << "\x00" << "\x00" << "\x00\x00\x00\x00" << "ab"
15
+ buffer.force_encoding(Encoding::BINARY)
14
16
  buffer_orig = buffer.dup
15
17
  assert_nil(Plum::Frame.parse!(buffer))
16
18
  assert_equal(buffer_orig, buffer)
@@ -18,7 +20,8 @@ class FrameTest < Minitest::Test
18
20
 
19
21
  def test_parse
20
22
  # R 0x1, stream_id 0x4, body "abc"
21
- buffer = "\x00\x00\x03" << "\x00" << "\x09" << "\x80\x00\x00\x04" << "abc" << "next_frame_data"
23
+ buffer = "\x00\x00\x03" << "\x00" << "\x09" << "\x00\x00\x00\x04" << "abc" << "next_frame_data"
24
+ buffer.force_encoding(Encoding::BINARY)
22
25
  frame = Plum::Frame.parse!(buffer)
23
26
  assert_equal(3, frame.length)
24
27
  assert_equal(:data, frame.type)
@@ -27,9 +27,7 @@ class HTTPConnectionNegotiationTest < Minitest::Test
27
27
  io = StringIO.new
28
28
  con = HTTPConnection.new(io)
29
29
  heads = nil
30
- con.on(:stream) {|stream|
31
- stream.on(:headers) {|_h| heads = _h.to_h }
32
- }
30
+ con.on(:headers) {|_, _h| heads = _h.to_h }
33
31
  req = "GET / HTTP/1.1\r\n" <<
34
32
  "Host: rhe.jp\r\n" <<
35
33
  "User-Agent: nya\r\n" <<
@@ -64,24 +64,21 @@ class HTTPSConnectionNegotiationTest < Minitest::Test
64
64
  client_thread = Thread.new {
65
65
  sock = TCPSocket.new("127.0.0.1", LISTEN_PORT)
66
66
  begin
67
- Timeout.timeout(3) {
68
- ctx = OpenSSL::SSL::SSLContext.new.tap {|ctx|
69
- ctx.alpn_protocols = ["h2"]
70
- ctx.ciphers = "AES256-GCM-SHA384"
71
- }
72
- ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
73
- ssl.connect
74
- ssl.write Connection::CLIENT_CONNECTION_PREFACE
75
- ssl.write Frame.settings.assemble
67
+ ctx = OpenSSL::SSL::SSLContext.new.tap {|ctx|
68
+ ctx.alpn_protocols = ["h2"]
69
+ ctx.ciphers = "AES256-GCM-SHA384"
76
70
  }
77
- rescue Timeout::Error
78
- flunk "client timeout"
71
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
72
+ ssl.connect
73
+ ssl.write Connection::CLIENT_CONNECTION_PREFACE
74
+ ssl.write Frame.settings.assemble
75
+ sleep
79
76
  ensure
80
77
  sock.close
81
78
  end
82
79
  }
83
- client_thread.join
84
80
  server_thread.join
81
+ client_thread.kill
85
82
 
86
83
  flunk "test not run" unless run
87
84
  end
data/test/utils/server.rb CHANGED
@@ -31,7 +31,7 @@ module ServerUtils
31
31
  end
32
32
 
33
33
  def sent_frames(con = nil)
34
- resp = (con || @_con).io.string.dup
34
+ resp = (con || @_con).sock.string.dup.force_encoding(Encoding::BINARY)
35
35
  frames = []
36
36
  while f = Frame.parse!(resp)
37
37
  frames << f
@@ -40,10 +40,10 @@ module ServerUtils
40
40
  end
41
41
 
42
42
  def capture_frames(con = nil, &blk)
43
- io = (con || @_con).io
43
+ io = (con || @_con).sock
44
44
  pos = io.string.bytesize
45
45
  blk.call
46
- resp = io.string.byteslice(pos, io.string.bytesize - pos)
46
+ resp = io.string.byteslice(pos, io.string.bytesize - pos).force_encoding(Encoding::BINARY)
47
47
  frames = []
48
48
  while f = Frame.parse!(resp)
49
49
  frames << f
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - rhenium
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-19 00:00:00.000000000 Z
11
+ date: 2015-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -139,7 +153,8 @@ dependencies:
139
153
  description: A minimal implementation of HTTP/2 server.
140
154
  email:
141
155
  - k@rhe.jp
142
- executables: []
156
+ executables:
157
+ - plum
143
158
  extensions: []
144
159
  extra_rdoc_files: []
145
160
  files:
@@ -151,7 +166,9 @@ files:
151
166
  - README.md
152
167
  - Rakefile
153
168
  - bin/.gitkeep
169
+ - bin/plum
154
170
  - examples/non_tls_server.rb
171
+ - examples/rack.ru
155
172
  - examples/static_server.rb
156
173
  - lib/plum.rb
157
174
  - lib/plum/binary_string.rb
@@ -170,9 +187,17 @@ files:
170
187
  - lib/plum/hpack/huffman.rb
171
188
  - lib/plum/http_connection.rb
172
189
  - lib/plum/https_connection.rb
190
+ - lib/plum/rack.rb
191
+ - lib/plum/rack/cli.rb
192
+ - lib/plum/rack/config.rb
193
+ - lib/plum/rack/connection.rb
194
+ - lib/plum/rack/dsl.rb
195
+ - lib/plum/rack/listener.rb
196
+ - lib/plum/rack/server.rb
173
197
  - lib/plum/stream.rb
174
198
  - lib/plum/stream_utils.rb
175
199
  - lib/plum/version.rb
200
+ - lib/rack/handler/plum.rb
176
201
  - plum.gemspec
177
202
  - test/plum/connection/test_handle_frame.rb
178
203
  - test/plum/hpack/test_context.rb