plum 0.2.2 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3671dbbd1de29adc6a254e5c95cef0a20cce6970
4
- data.tar.gz: efd31623c389dbefb5d8d46de2f4f1233d72470e
3
+ metadata.gz: 01ad5df7a9751026ab7dfef37ff76b47de82bdb8
4
+ data.tar.gz: 7618f58b020da6661e9d5c89974d1fe175d89651
5
5
  SHA512:
6
- metadata.gz: e8dd0b9146d5261854136d69c4bcbbe471f31daeb1785b3219779c5ad8daa76bfab82763a383e308b2c17f2fe6ec127a7c8b293ab59a33c22bb7123ecd9ccaab
7
- data.tar.gz: 035f11f0270f7fb7c7c95cae95993edcf4e924d6f10643928a53f5bcbee8501b13f66b537eb2875607abf2b87f9c0fd3fd16de1241c525d91742505f2b6909f8
6
+ metadata.gz: 11175e66083c4fae1c42301c35ce8b56b2fcf6540dffe68eb890a0a5a56e79e90f34ca744c4c63f20a834abfdba787857c5c906852aae6c9e6ae450af9f58ca6
7
+ data.tar.gz: 95ce217f9ed15a9508d67f13903354aed565a58876d15b98a9458c8b400736bfe1def86d9ab091c3ff29902dbbd699986cb08e78216e54419536036216511473
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
+ /.ruby-version
3
4
  /Gemfile.lock
4
5
  /_yardoc/
5
6
  /coverage/
@@ -4,11 +4,11 @@ addons:
4
4
  repo_token: f5092ab344fac7f2de9d7332e00597642a4d24e3d560f7d7f329172a2e5a2def
5
5
  install:
6
6
  - echo openssl_url=https://www.openssl.org/source >> $rvm_path/user/db
7
- - echo openssl_version=1.0.2d >> $rvm_path/user/db
7
+ - echo openssl_version=1.0.2e >> $rvm_path/user/db
8
8
  - rvm pkg install openssl
9
9
  - $rvm_path/usr/bin/openssl version
10
- - rvm install 2.2.3-alpn --patch https://gist.githubusercontent.com/rhenium/b1711edcc903e8887a51/raw/2309e469f5a3ba15917d804ac61b19e62b3d8faf/ruby-openssl-alpn-no-tests-and-docs.patch --with-openssl-dir=$rvm_path/usr
11
- - rvm use 2.2.3-alpn
10
+ - rvm install ruby-head --with-openssl-dir=$rvm_path/usr
11
+ - rvm use ruby-head
12
12
  - bundle install
13
13
  script:
14
14
  - bundle exec rake test
data/README.md CHANGED
@@ -6,9 +6,7 @@ WARNING: Plum is currently under heavy development. You *will* encounter bugs wh
6
6
  [![Circle CI](https://circleci.com/gh/rhenium/plum.svg?style=svg)](https://circleci.com/gh/rhenium/plum) [![Build Status](https://travis-ci.org/rhenium/plum.png?branch=master)](https://travis-ci.org/rhenium/plum) [![Code Climate](https://codeclimate.com/github/rhenium/plum/badges/gpa.svg)](https://codeclimate.com/github/rhenium/plum) [![Test Coverage](https://codeclimate.com/github/rhenium/plum/badges/coverage.svg)](https://codeclimate.com/github/rhenium/plum/coverage)
7
7
 
8
8
  ## Requirements
9
- * Ruby
10
- * Ruby 2.2 with [ALPN support patch](https://gist.github.com/rhenium/b1711edcc903e8887a51) and [ECDH support patch (r51348)](https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/51348/diff?format=diff)
11
- * or latest Ruby 2.3.0-dev
9
+ * Ruby 2.3
12
10
  * OpenSSL 1.0.2 or newer (HTTP/2 requires ALPN)
13
11
  * Optional:
14
12
  * [http_parser.rb gem](https://rubygems.org/gems/http_parser.rb) (HTTP/1.x parser; if you use "http" URI scheme)
@@ -63,7 +61,7 @@ If the server does't support HTTP/2, `Plum::Client` tries to use HTTP/1.x instea
63
61
  +-----------------+
64
62
  v "https"
65
63
  +-----------------+
66
- | ALPN(/NPN) | failed
64
+ | ALPN | failed
67
65
  | negotiation |-------> HTTP/1.x
68
66
  +-----------------+
69
67
  | "h2"
data/circle.yml CHANGED
@@ -4,15 +4,15 @@ machine:
4
4
  dependencies:
5
5
  pre:
6
6
  - echo openssl_url=https://www.openssl.org/source >> $rvm_path/user/db
7
- - echo openssl_version=1.0.2d >> $rvm_path/user/db
7
+ - echo openssl_version=1.0.2e >> $rvm_path/user/db
8
8
  - rvm pkg install openssl
9
9
  - >
10
10
  case $CIRCLE_NODE_INDEX in
11
11
  0)
12
- rvm install 2.2.3-alpn --with-openssl-dir=$rvm_path/usr --patch https://gist.githubusercontent.com/rhenium/b1711edcc903e8887a51/raw/2309e469f5a3ba15917d804ac61b19e62b3d8faf/ruby-openssl-alpn-no-tests-and-docs.patch
13
- rvm use 2.2.3-alpn --default
12
+ rvm install ruby-2.3.0 --with-openssl-dir=$rvm_path/usr
13
+ rvm use ruby-2.3.0 --default
14
14
  ;;
15
- 1)
15
+ 0)
16
16
  rvm install ruby-head --with-openssl-dir=$rvm_path/usr
17
17
  rvm use ruby-head --default
18
18
  ;;
@@ -25,7 +25,7 @@ loop do
25
25
  next
26
26
  end
27
27
 
28
- plum = Plum::HTTPServerConnection.new(sock)
28
+ plum = Plum::HTTPServerConnection.new(sock.method(:write))
29
29
 
30
30
  plum.on(:frame) do |frame|
31
31
  log(id, frame.stream_id, "recv: #{frame.inspect}")
@@ -0,0 +1,24 @@
1
+ log "logs/plum.log"
2
+ debug false
3
+ server_push true
4
+ threaded false # create a new thread per request
5
+ fallback_legacy "127.0.0.1:8080" # forward if client doesn't support HTTP/2
6
+
7
+ user "nobody"
8
+ group "nobody"
9
+
10
+ # listeners may be multiple
11
+ listener :unix, { path: "/tmp/plum.sock", mode: 600 }
12
+ listener :tcp, { hostname: "0.0.0.0", port: 80 }
13
+ listener :tls, {
14
+ hostname: "0.0.0.0",
15
+ port: 443,
16
+ certificate: "/path/to/cert", # chained certifcate is acceptable
17
+ certificate_key: "/path/to/key",
18
+ sni: {
19
+ "rhe.jp" => { # SNI, key must be String. If none matches, default certificate (above) is used
20
+ certificate: "/path/to/rhe.jp/cert",
21
+ certificate_key: "/path/to/rhe.jp/key"
22
+ },
23
+ }
24
+ }
@@ -43,7 +43,7 @@ loop do
43
43
  next
44
44
  end
45
45
 
46
- plum = Plum::HTTPSServerConnection.new(sock)
46
+ plum = Plum::ServerConnection.new(sock.method(:write))
47
47
 
48
48
  plum.on(:frame) do |frame|
49
49
  log(id, frame.stream_id, "recv: #{frame.inspect}")
@@ -21,7 +21,7 @@ require "plum/connection"
21
21
  require "plum/stream_utils"
22
22
  require "plum/stream"
23
23
  require "plum/server/connection"
24
- require "plum/server/https_connection"
24
+ require "plum/server/ssl_socket_connection"
25
25
  require "plum/server/http_connection"
26
26
  require "plum/client"
27
27
  require "plum/client/response"
@@ -143,8 +143,7 @@ module Plum
143
143
  @socket.connect
144
144
  @socket.post_connection_check(@config[:hostname]) if ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
145
145
 
146
- @socket.respond_to?(:alpn_protocol) && @socket.alpn_protocol == "h2" ||
147
- @socket.respond_to?(:npn_protocol) && @socket.npn_protocol == "h2"
146
+ @socket.alpn_protocol == "h2"
148
147
  end
149
148
  end
150
149
 
@@ -175,15 +174,8 @@ module Plum
175
174
  cert_store.set_default_paths
176
175
  ctx.cert_store = cert_store
177
176
  if @config[:http2]
178
- ctx.ciphers = "ALL:!" + HTTPSServerConnection::CIPHER_BLACKLIST.join(":!")
179
- if ctx.respond_to?(:alpn_protocols)
180
- ctx.alpn_protocols = ["h2", "http/1.1"]
181
- end
182
- if ctx.respond_to?(:npn_select_cb) # TODO: RFC 7540 does not define protocol negotiation with NPN
183
- ctx.npn_select_cb = -> protocols {
184
- protocols.include?("h2") ? "h2" : protocols.first
185
- }
186
- end
177
+ ctx.ciphers = "ALL:!" + SSLSocketServerConnection::CIPHER_BLACKLIST.join(":!")
178
+ ctx.alpn_protocols = ["h2", "http/1.1"]
187
179
  end
188
180
  ctx
189
181
  end
@@ -64,6 +64,10 @@ module Plum
64
64
  response._fail
65
65
  raise ex
66
66
  }
67
+ stream.on(:local_stream_error) { |type|
68
+ response.fail
69
+ raise LocalStreamError.new(type)
70
+ }
67
71
  response
68
72
  end
69
73
 
@@ -19,9 +19,9 @@ module Plum
19
19
 
20
20
  # Sends GOAWAY frame to the peer and closes the connection.
21
21
  # @param error_type [Symbol] The error type to be contained in the GOAWAY frame.
22
- def goaway(error_type = :no_error)
22
+ def goaway(error_type = :no_error, message = "")
23
23
  last_id = @max_stream_ids.max
24
- send_immediately Frame.goaway(last_id, error_type)
24
+ send_immediately Frame.goaway(last_id, error_type, message)
25
25
  end
26
26
 
27
27
  # Returns whether peer enables server push or not
@@ -5,18 +5,14 @@ module Plum
5
5
  # @param name [Symbol] The name of event.
6
6
  # @yield Gives event-specific parameters.
7
7
  def on(name, &blk)
8
- (callbacks[name] ||= []) << blk
8
+ @callbacks ||= {}
9
+ (@callbacks[name] ||= []) << blk
9
10
  end
10
11
 
11
12
  # Invokes an event and call handlers with args.
12
13
  # @param name [Symbol] The identifier of event.
13
14
  def callback(name, *args)
14
- (cbs = callbacks[name]) && cbs.each {|cb| cb.call(*args) }
15
- end
16
-
17
- private
18
- def callbacks
19
- @callbacks ||= {}
15
+ @callbacks&.[](name)&.each { |cb| cb.call(*args) }
20
16
  end
21
17
  end
22
18
  end
@@ -17,7 +17,7 @@ module Plum
17
17
  # @param message [String] Additional debug data.
18
18
  # @see RFC 7540 Section 6.8
19
19
  def goaway(last_id, error_type, message = "")
20
- payload = String.new.push_uint32((last_id || 0) | (0 << 31))
20
+ payload = String.new.push_uint32(last_id)
21
21
  .push_uint32(HTTPError::ERROR_CODES[error_type])
22
22
  .push(message)
23
23
  Frame.new(type: :goaway, stream_id: 0, payload: payload)
@@ -55,10 +55,9 @@ module Plum
55
55
  # @param stream_id [Integer] The stream ID.
56
56
  # @param payload [String] Payload.
57
57
  # @param end_stream [Boolean] add END_STREAM flag
58
- def data(stream_id, payload, end_stream: false)
59
- payload = payload.b if payload && payload.encoding != Encoding::BINARY
60
- fval = 0
61
- fval += 1 if end_stream
58
+ def data(stream_id, payload = "", end_stream: false)
59
+ payload = payload.b if payload&.encoding != Encoding::BINARY
60
+ fval = end_stream ? 1 : 0
62
61
  Frame.new(type_value: 0, stream_id: stream_id, flags_value: fval, payload: payload)
63
62
  end
64
63
 
@@ -68,8 +67,7 @@ module Plum
68
67
  # @param end_stream [Boolean] add END_STREAM flag
69
68
  # @param end_headers [Boolean] add END_HEADERS flag
70
69
  def headers(stream_id, encoded, end_stream: false, end_headers: false)
71
- fval = 0
72
- fval += 1 if end_stream
70
+ fval = end_stream ? 1 : 0
73
71
  fval += 4 if end_headers
74
72
  Frame.new(type_value: 1, stream_id: stream_id, flags_value: fval, payload: encoded)
75
73
  end
@@ -82,8 +80,7 @@ module Plum
82
80
  def push_promise(stream_id, new_id, encoded, end_headers: false)
83
81
  payload = String.new.push_uint32(new_id)
84
82
  .push(encoded)
85
- fval = 0
86
- fval += 4 if end_headers
83
+ fval = end_headers ? 4 : 0
87
84
  Frame.new(type: :push_promise, stream_id: stream_id, flags_value: fval, payload: payload)
88
85
  end
89
86
 
@@ -92,7 +89,7 @@ module Plum
92
89
  # @param payload [String] Payload.
93
90
  # @param end_headers [Boolean] add END_HEADERS flag
94
91
  def continuation(stream_id, payload, end_headers: false)
95
- Frame.new(type: :continuation, stream_id: stream_id, flags_value: (end_headers && 4 || 0), payload: payload)
92
+ Frame.new(type: :continuation, stream_id: stream_id, flags_value: (end_headers ? 4 : 0), payload: payload)
96
93
  end
97
94
  end
98
95
  end
@@ -36,18 +36,21 @@ module Plum
36
36
  end
37
37
 
38
38
  def search(name, value)
39
- pr = proc { |n, v|
40
- n == name && (!value || v == value)
41
- }
39
+ si = STATIC_TABLE.index { |n, v| n == name && v == value }
40
+ return si + 1 if si
41
+ di = @dynamic_table.index { |n, v| n == name && v == value }
42
+ return di + STATIC_TABLE_SIZE + 1 if di
43
+ end
42
44
 
43
- si = STATIC_TABLE.index &pr
45
+ def search_half(name)
46
+ si = STATIC_TABLE.index { |n, v| n == name }
44
47
  return si + 1 if si
45
- di = @dynamic_table.index &pr
48
+ di = @dynamic_table.index { |n, v| n == name }
46
49
  return di + STATIC_TABLE_SIZE + 1 if di
47
50
  end
48
51
 
49
52
  def evict
50
- while @limit && @size > @limit
53
+ while @size > @limit
51
54
  name, value = @dynamic_table.pop
52
55
  @size -= name.bytesize + value.bytesize + 32
53
56
  end
@@ -12,13 +12,13 @@ module Plum
12
12
  @huffman = huffman
13
13
  end
14
14
  def encode(headers)
15
- out = String.new.force_encoding(Encoding::BINARY)
15
+ out = "".b
16
16
  headers.each do |name, value|
17
17
  name = name.to_s
18
18
  value = value.to_s
19
19
  if index = search(name, value)
20
20
  out << encode_indexed(index)
21
- elsif index = search(name, nil)
21
+ elsif index = search_half(name)
22
22
  out << encode_half_indexed(index, value)
23
23
  else
24
24
  out << encode_literal(name, value)
@@ -35,7 +35,7 @@ module Plum
35
35
 
36
36
  def transform_options
37
37
  if @options[:config]
38
- dsl = DSL::Config.new.instance_eval(File.read(@options[:config]))
38
+ dsl = DSL::Config.new.tap { |c| c.instance_eval(File.read(@options[:config])) }
39
39
  config = dsl.config
40
40
  else
41
41
  config = Config.new
@@ -49,6 +49,14 @@ module Plum
49
49
  @config[:fallback_legacy_host] = h
50
50
  @config[:fallback_legacy_port] = p.to_i
51
51
  end
52
+
53
+ def user(username)
54
+ @config[:user] = username
55
+ end
56
+
57
+ def group(groupname)
58
+ @config[:group] = groupname
59
+ end
52
60
  end
53
61
  end
54
62
  end
@@ -25,7 +25,7 @@ module Plum
25
25
  end
26
26
 
27
27
  def plum(sock)
28
- ::Plum::HTTPServerConnection.new(sock)
28
+ ::Plum::HTTPServerConnection.new(sock.method(:write))
29
29
  end
30
30
  end
31
31
 
@@ -41,34 +41,42 @@ module Plum
41
41
 
42
42
  ctx = OpenSSL::SSL::SSLContext.new
43
43
  ctx.ssl_version = :TLSv1_2
44
- ctx.alpn_select_cb = -> protocols {
45
- if protocols.include?("h2")
46
- "h2"
44
+ ctx.alpn_select_cb = -> (protocols) { protocols.include?("h2") ? "h2" : protocols.first }
45
+ ctx.tmp_ecdh_callback = -> (sock, ise, keyl) { OpenSSL::PKey::EC.new("prime256v1") }
46
+ *ctx.extra_chain_cert, ctx.cert = parse_chained_cert(cert)
47
+ ctx.key = OpenSSL::PKey::RSA.new(key)
48
+ ctx.servername_cb = proc { |sock, hostname|
49
+ if host = lc[:sni]&.[](hostname)
50
+ new_ctx = ctx.dup
51
+ *new_ctx.extra_chain_cert, new_ctx.cert = parse_chained_cert(File.read(host[:certificate]))
52
+ new_ctx.key = OpenSSL::PKey::RSA.new(File.read(host[:certificate_key]))
53
+ new_ctx
47
54
  else
48
- protocols.first
55
+ ctx
49
56
  end
50
57
  }
51
- ctx.tmp_ecdh_callback = -> (sock, ise, keyl) { OpenSSL::PKey::EC.new("prime256v1") }
52
- ctx.cert = OpenSSL::X509::Certificate.new(cert)
53
- ctx.key = OpenSSL::PKey::RSA.new(key)
54
58
  tcp_server = ::TCPServer.new(lc[:hostname], lc[:port])
55
59
  @server = OpenSSL::SSL::SSLServer.new(tcp_server, ctx)
56
60
  @server.start_immediately = false
57
61
  end
58
62
 
63
+ def parse_chained_cert(str)
64
+ str.scan(/-----BEGIN CERTIFICATE.+?END CERTIFICATE-----/m).map { |s| OpenSSL::X509::Certificate.new(s) }
65
+ end
66
+
59
67
  def to_io
60
68
  @server.to_io
61
69
  end
62
70
 
63
71
  def plum(sock)
64
- raise ::Plum::LegacyHTTPError.new("client doesn't offered h2 with ALPN", nil) unless sock.alpn_protocol == "h2"
65
- ::Plum::HTTPSServerConnection.new(sock)
72
+ raise ::Plum::LegacyHTTPError.new("client didn't offer h2 with ALPN", nil) unless sock.alpn_protocol == "h2"
73
+ ::Plum::ServerConnection.new(sock.method(:write))
66
74
  end
67
75
 
68
76
  private
69
77
  # returns: [cert, key]
70
78
  def dummy_key
71
- puts "WARNING: Generating new dummy certificate..."
79
+ STDERR.puts "WARNING: Generating new dummy certificate..."
72
80
 
73
81
  key = OpenSSL::PKey::RSA.new(2048)
74
82
  cert = OpenSSL::X509::Certificate.new
@@ -84,13 +92,10 @@ module Plum
84
92
  ef.issuer_certificate = cert
85
93
  cert.extensions = [
86
94
  ef.create_extension("basicConstraints", "CA:TRUE", true),
87
- ef.create_extension("subjectKeyIdentifier", "hash"),
88
95
  ]
89
- cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
90
-
91
- cert.sign key, OpenSSL::Digest::SHA1.new
96
+ cert.sign(key, OpenSSL::Digest::SHA256.new)
92
97
 
93
- [cert, key]
98
+ [cert.to_s, key.to_s]
94
99
  end
95
100
  end
96
101
 
@@ -122,7 +127,7 @@ module Plum
122
127
  end
123
128
 
124
129
  def plum(sock)
125
- ::Plum::HTTPSServerConnection.new(sock)
130
+ ::Plum::ServerConnection.new(sock.method(:write))
126
131
  end
127
132
  end
128
133
  end
@@ -17,6 +17,10 @@ module Plum
17
17
 
18
18
  @logger.info("Plum #{::Plum::VERSION}")
19
19
  @logger.info("Config: #{config}")
20
+
21
+ if @config[:user]
22
+ drop_privileges
23
+ end
20
24
  end
21
25
 
22
26
  def start
@@ -100,6 +104,24 @@ module Plum
100
104
  ensure
101
105
  upstream.close if upstream
102
106
  end
107
+
108
+ def drop_privileges
109
+ begin
110
+ user = @config[:user]
111
+ group = @config[:group] || user
112
+ @logger.info "Dropping process privilege to #{user}:#{group}"
113
+
114
+ cuid, cgid = Process.euid, Process.egid
115
+ tuid, tgid = Etc.getpwnam(user).uid, Etc.getgrnam(group).gid
116
+
117
+ Process.initgroups(user, tgid)
118
+ Process::GID.change_privilege(tgid)
119
+ Process::UID.change_privilege(tuid)
120
+ rescue Errno::EPERM => e
121
+ @ogger.fatal "Could not change privilege: #{e}"
122
+ exit 2
123
+ end
124
+ end
103
125
  end
104
126
  end
105
127
  end
@@ -28,9 +28,6 @@ module Plum
28
28
  while !@sock.closed? && !@sock.eof?
29
29
  @plum << @sock.readpartial(1024)
30
30
  end
31
- rescue Errno::EPIPE, Errno::ECONNRESET => e
32
- rescue StandardError => e
33
- @logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}")
34
31
  ensure
35
32
  @request_thread.each { |stream, thread| thread.kill }
36
33
  stop
@@ -38,10 +35,6 @@ module Plum
38
35
 
39
36
  private
40
37
  def setup_plum
41
- @plum.on(:frame) { |f| puts "recv:#{f.inspect}" }
42
- @plum.on(:send_frame) { |f|
43
- puts "send:#{f.inspect}" unless f.type == :data
44
- }
45
38
  @plum.on(:connection_error) { |ex| @logger.error(ex) }
46
39
 
47
40
  # @plum.on(:stream) { |stream| @logger.debug("new stream: #{stream}") }
@@ -90,7 +83,7 @@ module Plum
90
83
  end
91
84
  else
92
85
  body.each { |part| stream.send_data(part, end_stream: false) }
93
- stream.send_data(nil, end_stream: true)
86
+ stream.send_data(end_stream: true)
94
87
  end
95
88
  ensure
96
89
  body.close if body.respond_to?(:close)
@@ -98,47 +91,53 @@ module Plum
98
91
  end
99
92
 
100
93
  def extract_push(reqheaders, extheaders)
101
- if @config[:server_push] && @plum.push_enabled? && pushs = extheaders["plum.serverpush"]
102
- authority = reqheaders.find { |k, v| k == ":authority" }[1]
103
- scheme = reqheaders.find { |k, v| k == ":scheme" }[1]
104
-
105
- pushs.split(";").map { |push|
106
- method, path = push.split(" ", 2)
107
- {
108
- ":authority" => authority,
109
- ":method" => method.upcase,
110
- ":scheme" => scheme,
111
- ":path" => path
112
- }
94
+ pushs = extheaders["plum.serverpush"]
95
+ return nil unless pushs
96
+
97
+ authority = reqheaders.find { |k, v| k == ":authority" }[1]
98
+ scheme = reqheaders.find { |k, v| k == ":scheme" }[1]
99
+
100
+ pushs.split(";").map { |push|
101
+ method, path = push.split(" ", 2)
102
+ {
103
+ ":authority" => authority,
104
+ ":method" => method.upcase,
105
+ ":scheme" => scheme,
106
+ ":path" => path
113
107
  }
114
- else
115
- []
116
- end
108
+ }
117
109
  end
118
110
 
119
111
  def handle_request(stream, headers, data)
120
112
  env = new_env(headers, data)
121
113
  r_status, r_rawheaders, r_body = @app.call(env)
122
114
  r_headers, r_extheaders = extract_headers(r_status, r_rawheaders)
115
+ if @config[:server_push] && @plum.push_enabled?
116
+ push_preqs = extract_push(headers, r_extheaders)
117
+ end
123
118
 
124
119
  no_body = r_body.respond_to?(:empty?) && r_body.empty?
125
120
 
126
121
  stream.send_headers(r_headers, end_stream: no_body)
127
122
 
128
- push_sts = extract_push(headers, r_extheaders).map { |preq|
129
- [stream.promise(preq), preq]
130
- }
123
+ if push_preqs
124
+ push_preqs.map! { |preq|
125
+ [stream.promise(preq), preq]
126
+ }
127
+ end
131
128
 
132
129
  send_body(stream, r_body) unless no_body
133
130
 
134
- push_sts.each { |st, preq|
135
- penv = new_env(preq, "".b)
136
- p_status, p_h, p_body = @app.call(penv)
137
- p_headers, _ = extract_headers(p_status, p_h)
138
- pno_body = p_body.respond_to?(:empty?) && p_body.empty?
139
- st.send_headers(p_headers, end_stream: pno_body)
140
- send_body(st, p_body) unless pno_body
141
- }
131
+ if push_preqs
132
+ push_preqs.each { |st, preq|
133
+ penv = new_env(preq, "".b)
134
+ p_status, p_h, p_body = @app.call(penv)
135
+ p_headers, _ = extract_headers(p_status, p_h)
136
+ pno_body = p_body.respond_to?(:empty?) && p_body.empty?
137
+ st.send_headers(p_headers, end_stream: pno_body)
138
+ send_body(st, p_body) unless pno_body
139
+ }
140
+ end
142
141
 
143
142
  @request_thread.delete(stream)
144
143
  end
@@ -3,20 +3,11 @@ using Plum::BinaryString
3
3
 
4
4
  module Plum
5
5
  class HTTPServerConnection < ServerConnection
6
- attr_reader :sock
7
-
8
- def initialize(sock, local_settings = {})
6
+ def initialize(writer, local_settings = {})
9
7
  require "http/parser"
10
- @sock = sock
11
8
  @negobuf = String.new
12
9
  @_http_parser = setup_parser
13
- super(@sock.method(:write), local_settings)
14
- end
15
-
16
- # Closes the socket.
17
- def close
18
- super
19
- @sock.close
10
+ super(writer, local_settings)
20
11
  end
21
12
 
22
13
  private
@@ -68,7 +59,7 @@ module Plum
68
59
  "Server: plum/#{Plum::VERSION}\r\n" +
69
60
  "\r\n"
70
61
 
71
- @sock.write(resp)
62
+ @writer.call(resp)
72
63
  end
73
64
 
74
65
  def process_first_request(parser, headers, body)
@@ -1,6 +1,6 @@
1
1
  # -*- frozen-string-literal: true -*-
2
2
  module Plum
3
- class HTTPSServerConnection < ServerConnection
3
+ class SSLSocketServerConnection < ServerConnection
4
4
  attr_reader :sock
5
5
 
6
6
  def initialize(sock, local_settings = {})
@@ -237,11 +237,7 @@ module Plum
237
237
  @state = :closed # MUST NOT send RST_STREAM
238
238
 
239
239
  error_code = frame.payload.uint32
240
- if error_code > 0
241
- raise LocalStreamError.new(HTTPError::ERROR_CODES.key(error_code))
242
- else
243
- callback(:rst_stream, frame)
244
- end
240
+ callback(:rst_stream, HTTPError::ERROR_CODES.key(error_code))
245
241
  end
246
242
 
247
243
  # override EventEmitter
@@ -28,7 +28,7 @@ module Plum
28
28
  # Sends DATA frame. If the data is larger than MAX_FRAME_SIZE, DATA frame will be splitted.
29
29
  # @param data [String, IO] The data to send.
30
30
  # @param end_stream [Boolean] Set END_STREAM flag or not.
31
- def send_data(data, end_stream: true)
31
+ def send_data(data = "", end_stream: true)
32
32
  max = @connection.remote_settings[:max_frame_size]
33
33
  if data.is_a?(IO)
34
34
  until data.eof?
@@ -1,4 +1,4 @@
1
1
  # -*- frozen-string-literal: true -*-
2
2
  module Plum
3
- VERSION = "0.2.2"
3
+ VERSION = "0.2.8"
4
4
  end
@@ -112,7 +112,7 @@ class ClientTest < Minitest::Test
112
112
  begin
113
113
  Timeout.timeout(1) {
114
114
  sock = ssl_server.accept
115
- plum = HTTPSServerConnection.new(sock)
115
+ plum = ServerConnection.new(sock.method(:write))
116
116
 
117
117
  plum.on(:stream) { |stream|
118
118
  headers = data = nil
@@ -38,6 +38,8 @@ class HPACKContextTest < Minitest::Test
38
38
  context = new_context
39
39
  i1 = context.search(":method", "POST")
40
40
  assert_equal(3, i1)
41
+ i2 = context.search_half(":method")
42
+ assert_equal(2, i2)
41
43
  end
42
44
 
43
45
  def test_search_dynamic
@@ -48,7 +50,7 @@ class HPACKContextTest < Minitest::Test
48
50
  assert_equal(63, i1)
49
51
  i2 = context.search("あああ", "AAA")
50
52
  assert_equal(nil, i2)
51
- i3 = context.search("あああ", nil)
53
+ i3 = context.search_half("あああ")
52
54
  assert_equal(62, i3)
53
55
  end
54
56
 
@@ -4,21 +4,21 @@ using Plum::BinaryString
4
4
 
5
5
  class HTTPSConnectionNegotiationTest < Minitest::Test
6
6
  def test_server_must_raise_cprotocol_error_invalid_magic_short
7
- con = HTTPSServerConnection.new(StringIO.new)
7
+ con = ServerConnection.new(StringIO.new.method(:write))
8
8
  assert_connection_error(:protocol_error) {
9
9
  con << "HELLO"
10
10
  }
11
11
  end
12
12
 
13
13
  def test_server_must_raise_cprotocol_error_invalid_magic_long
14
- con = HTTPSServerConnection.new(StringIO.new)
14
+ con = ServerConnection.new(StringIO.new.method(:write))
15
15
  assert_connection_error(:protocol_error) {
16
16
  con << ("HELLO" * 100) # over 24
17
17
  }
18
18
  end
19
19
 
20
20
  def test_server_must_raise_cprotocol_error_non_settings_after_magic
21
- con = HTTPSServerConnection.new(StringIO.new)
21
+ con = ServerConnection.new(StringIO.new.method(:write))
22
22
  con << Connection::CLIENT_CONNECTION_PREFACE
23
23
  assert_connection_error(:protocol_error) {
24
24
  con << Frame.new(type: :window_update, stream_id: 0, payload: "".push_uint32(1)).assemble
@@ -27,7 +27,7 @@ class HTTPSConnectionNegotiationTest < Minitest::Test
27
27
 
28
28
  def test_server_accept_fragmented_magic
29
29
  magic = Connection::CLIENT_CONNECTION_PREFACE
30
- con = HTTPSServerConnection.new(StringIO.new)
30
+ con = ServerConnection.new(StringIO.new.method(:write))
31
31
  assert_no_error {
32
32
  con << magic[0...5]
33
33
  con << magic[5..-1]
@@ -49,7 +49,7 @@ class HTTPSConnectionNegotiationTest < Minitest::Test
49
49
  begin
50
50
  Timeout.timeout(3) {
51
51
  sock = ssl_server.accept
52
- plum = HTTPSServerConnection.new(sock)
52
+ plum = SSLSocketServerConnection.new(sock)
53
53
  assert_connection_error(:inadequate_security) {
54
54
  run = true
55
55
  while !sock.closed? && !sock.eof?
@@ -5,7 +5,8 @@ using Plum::BinaryString
5
5
  class HTTPConnectionNegotiationTest < Minitest::Test
6
6
  ## with Prior Knowledge (same as over TLS)
7
7
  def test_server_must_raise_cprotocol_error_non_settings_after_magic
8
- con = HTTPServerConnection.new(StringIO.new)
8
+ io = StringIO.new
9
+ con = HTTPServerConnection.new(io.method(:write))
9
10
  con << Connection::CLIENT_CONNECTION_PREFACE
10
11
  assert_connection_error(:protocol_error) {
11
12
  con << Frame.new(type: :window_update, stream_id: 0, payload: "".push_uint32(1)).assemble
@@ -14,7 +15,8 @@ class HTTPConnectionNegotiationTest < Minitest::Test
14
15
 
15
16
  def test_server_accept_fragmented_magic
16
17
  magic = Connection::CLIENT_CONNECTION_PREFACE
17
- con = HTTPServerConnection.new(StringIO.new)
18
+ io = StringIO.new
19
+ con = HTTPServerConnection.new(io.method(:write))
18
20
  assert_no_error {
19
21
  con << magic[0...5]
20
22
  con << magic[5..-1]
@@ -25,7 +27,7 @@ class HTTPConnectionNegotiationTest < Minitest::Test
25
27
  ## with HTTP/1.1 Upgrade
26
28
  def test_server_accept_upgrade
27
29
  io = StringIO.new
28
- con = HTTPServerConnection.new(io)
30
+ con = HTTPServerConnection.new(io.method(:write))
29
31
  heads = nil
30
32
  con.on(:headers) {|_, _h| heads = _h.to_h }
31
33
  req = "GET / HTTP/1.1\r\n" <<
@@ -47,7 +49,7 @@ class HTTPConnectionNegotiationTest < Minitest::Test
47
49
 
48
50
  def test_server_deny_non_upgrade
49
51
  io = StringIO.new
50
- con = HTTPServerConnection.new(io)
52
+ con = HTTPServerConnection.new(io.method(:write))
51
53
  req = "GET / HTTP/1.1\r\n" <<
52
54
  "Host: rhe.jp\r\n" <<
53
55
  "User-Agent: nya\r\n" <<
@@ -40,13 +40,14 @@ class StreamTest < Minitest::Test
40
40
 
41
41
  def test_stream_local_error
42
42
  open_server_connection { |con|
43
- stream = nil
44
- con.on(:headers) { |s| stream = s }
43
+ stream = type = nil
44
+ con.on(:rst_stream) { |s, t| stream = s; type = t }
45
45
 
46
46
  con << Frame.headers(1, "", end_headers: true).assemble
47
- assert_raises(LocalStreamError) {
48
- con << Frame.rst_stream(1, :frame_size_error).assemble
49
- }
47
+ con << Frame.rst_stream(1, :frame_size_error).assemble
48
+
49
+ assert_equal(1, stream.id)
50
+ assert_equal(:frame_size_error, type)
50
51
  }
51
52
  end
52
53
  end
@@ -2,7 +2,7 @@ unless ENV["SKIP_COVERAGE"]
2
2
  begin
3
3
  require "simplecov"
4
4
  require "codeclimate-test-reporter"
5
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov.formatters = [
6
6
  SimpleCov::Formatter::HTMLFormatter,
7
7
  CodeClimate::TestReporter::Formatter
8
8
  ]
@@ -2,8 +2,8 @@ require "timeout"
2
2
 
3
3
  module ServerUtils
4
4
  def open_server_connection(scheme = :https)
5
- io = StringIO.new
6
- @_con = (scheme == :https ? HTTPSServerConnection : HTTPServerConnection).new(io)
5
+ @_io = StringIO.new
6
+ @_con = (scheme == :https ? ServerConnection : HTTPServerConnection).new(@_io.method(:write))
7
7
  @_con << Connection::CLIENT_CONNECTION_PREFACE
8
8
  @_con << Frame.new(type: :settings, stream_id: 0).assemble
9
9
  if block_given?
@@ -30,8 +30,8 @@ module ServerUtils
30
30
  end
31
31
  end
32
32
 
33
- def sent_frames(con = nil)
34
- resp = (con || @_con).sock.string.dup.force_encoding(Encoding::BINARY)
33
+ def sent_frames(io = nil)
34
+ resp = (io || @_io).string.dup.force_encoding(Encoding::BINARY)
35
35
  frames = []
36
36
  while f = Frame.parse!(resp)
37
37
  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.2.2
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - rhenium
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-15 00:00:00.000000000 Z
11
+ date: 2016-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -171,6 +171,7 @@ files:
171
171
  - examples/client/large.rb
172
172
  - examples/client/twitter.rb
173
173
  - examples/non_tls_server.rb
174
+ - examples/rack-example-config.rb
174
175
  - examples/rack.ru
175
176
  - examples/static_server.rb
176
177
  - lib/plum.rb
@@ -204,7 +205,7 @@ files:
204
205
  - lib/plum/rack/session.rb
205
206
  - lib/plum/server/connection.rb
206
207
  - lib/plum/server/http_connection.rb
207
- - lib/plum/server/https_connection.rb
208
+ - lib/plum/server/ssl_socket_connection.rb
208
209
  - lib/plum/stream.rb
209
210
  - lib/plum/stream_utils.rb
210
211
  - lib/plum/version.rb
@@ -221,8 +222,8 @@ files:
221
222
  - test/plum/hpack/test_decoder.rb
222
223
  - test/plum/hpack/test_encoder.rb
223
224
  - test/plum/hpack/test_huffman.rb
225
+ - test/plum/server/test_connection.rb
224
226
  - test/plum/server/test_http_connection.rb
225
- - test/plum/server/test_https_connection.rb
226
227
  - test/plum/stream/test_handle_frame.rb
227
228
  - test/plum/test_binary_string.rb
228
229
  - test/plum/test_connection.rb
@@ -263,7 +264,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
264
  version: '0'
264
265
  requirements: []
265
266
  rubyforge_project:
266
- rubygems_version: 2.4.5.1
267
+ rubygems_version: 2.5.1
267
268
  signing_key:
268
269
  specification_version: 4
269
270
  summary: An HTTP/2 Library for Ruby
@@ -279,8 +280,8 @@ test_files:
279
280
  - test/plum/hpack/test_decoder.rb
280
281
  - test/plum/hpack/test_encoder.rb
281
282
  - test/plum/hpack/test_huffman.rb
283
+ - test/plum/server/test_connection.rb
282
284
  - test/plum/server/test_http_connection.rb
283
- - test/plum/server/test_https_connection.rb
284
285
  - test/plum/stream/test_handle_frame.rb
285
286
  - test/plum/test_binary_string.rb
286
287
  - test/plum/test_connection.rb