plum 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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