plum 0.2.2 → 0.2.8

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.
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