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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -3
- data/README.md +2 -4
- data/circle.yml +4 -4
- data/examples/non_tls_server.rb +1 -1
- data/examples/rack-example-config.rb +24 -0
- data/examples/static_server.rb +1 -1
- data/lib/plum.rb +1 -1
- data/lib/plum/client.rb +3 -11
- data/lib/plum/client/client_session.rb +4 -0
- data/lib/plum/connection_utils.rb +2 -2
- data/lib/plum/event_emitter.rb +3 -7
- data/lib/plum/frame_factory.rb +7 -10
- data/lib/plum/hpack/context.rb +9 -6
- data/lib/plum/hpack/encoder.rb +2 -2
- data/lib/plum/rack/cli.rb +1 -1
- data/lib/plum/rack/dsl.rb +8 -0
- data/lib/plum/rack/listener.rb +22 -17
- data/lib/plum/rack/server.rb +22 -0
- data/lib/plum/rack/session.rb +33 -34
- data/lib/plum/server/http_connection.rb +3 -12
- data/lib/plum/server/{https_connection.rb → ssl_socket_connection.rb} +1 -1
- data/lib/plum/stream.rb +1 -5
- data/lib/plum/stream_utils.rb +1 -1
- data/lib/plum/version.rb +1 -1
- data/test/plum/client/test_client.rb +1 -1
- data/test/plum/hpack/test_context.rb +3 -1
- data/test/plum/server/{test_https_connection.rb → test_connection.rb} +5 -5
- data/test/plum/server/test_http_connection.rb +6 -4
- data/test/plum/test_stream.rb +6 -5
- data/test/test_helper.rb +1 -1
- data/test/utils/server.rb +4 -4
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01ad5df7a9751026ab7dfef37ff76b47de82bdb8
|
4
|
+
data.tar.gz: 7618f58b020da6661e9d5c89974d1fe175d89651
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11175e66083c4fae1c42301c35ce8b56b2fcf6540dffe68eb890a0a5a56e79e90f34ca744c4c63f20a834abfdba787857c5c906852aae6c9e6ae450af9f58ca6
|
7
|
+
data.tar.gz: 95ce217f9ed15a9508d67f13903354aed565a58876d15b98a9458c8b400736bfe1def86d9ab091c3ff29902dbbd699986cb08e78216e54419536036216511473
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -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.
|
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
|
11
|
-
- rvm use
|
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
|
[](https://circleci.com/gh/rhenium/plum) [](https://travis-ci.org/rhenium/plum) [](https://codeclimate.com/github/rhenium/plum) [](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
|
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.
|
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.
|
13
|
-
rvm use 2.
|
12
|
+
rvm install ruby-2.3.0 --with-openssl-dir=$rvm_path/usr
|
13
|
+
rvm use ruby-2.3.0 --default
|
14
14
|
;;
|
15
|
-
|
15
|
+
0)
|
16
16
|
rvm install ruby-head --with-openssl-dir=$rvm_path/usr
|
17
17
|
rvm use ruby-head --default
|
18
18
|
;;
|
data/examples/non_tls_server.rb
CHANGED
@@ -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
|
+
}
|
data/examples/static_server.rb
CHANGED
data/lib/plum.rb
CHANGED
@@ -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/
|
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"
|
data/lib/plum/client.rb
CHANGED
@@ -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.
|
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:!" +
|
179
|
-
|
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
|
@@ -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
|
data/lib/plum/event_emitter.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/plum/frame_factory.rb
CHANGED
@@ -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(
|
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
|
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
|
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
|
data/lib/plum/hpack/context.rb
CHANGED
@@ -36,18 +36,21 @@ module Plum
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def search(name, value)
|
39
|
-
|
40
|
-
|
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
|
-
|
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
|
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 @
|
53
|
+
while @size > @limit
|
51
54
|
name, value = @dynamic_table.pop
|
52
55
|
@size -= name.bytesize + value.bytesize + 32
|
53
56
|
end
|
data/lib/plum/hpack/encoder.rb
CHANGED
@@ -12,13 +12,13 @@ module Plum
|
|
12
12
|
@huffman = huffman
|
13
13
|
end
|
14
14
|
def encode(headers)
|
15
|
-
out =
|
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 =
|
21
|
+
elsif index = search_half(name)
|
22
22
|
out << encode_half_indexed(index, value)
|
23
23
|
else
|
24
24
|
out << encode_literal(name, value)
|
data/lib/plum/rack/cli.rb
CHANGED
@@ -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
|
data/lib/plum/rack/dsl.rb
CHANGED
data/lib/plum/rack/listener.rb
CHANGED
@@ -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
|
-
|
46
|
-
|
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
|
-
|
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
|
65
|
-
::Plum::
|
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.
|
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::
|
130
|
+
::Plum::ServerConnection.new(sock.method(:write))
|
126
131
|
end
|
127
132
|
end
|
128
133
|
end
|
data/lib/plum/rack/server.rb
CHANGED
@@ -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
|
data/lib/plum/rack/session.rb
CHANGED
@@ -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(
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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
|
-
|
129
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
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(
|
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
|
-
@
|
62
|
+
@writer.call(resp)
|
72
63
|
end
|
73
64
|
|
74
65
|
def process_first_request(parser, headers, body)
|
data/lib/plum/stream.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/plum/stream_utils.rb
CHANGED
@@ -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?
|
data/lib/plum/version.rb
CHANGED
@@ -112,7 +112,7 @@ class ClientTest < Minitest::Test
|
|
112
112
|
begin
|
113
113
|
Timeout.timeout(1) {
|
114
114
|
sock = ssl_server.accept
|
115
|
-
plum =
|
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.
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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" <<
|
data/test/plum/test_stream.rb
CHANGED
@@ -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(:
|
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
|
-
|
48
|
-
|
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
|
data/test/test_helper.rb
CHANGED
data/test/utils/server.rb
CHANGED
@@ -2,8 +2,8 @@ require "timeout"
|
|
2
2
|
|
3
3
|
module ServerUtils
|
4
4
|
def open_server_connection(scheme = :https)
|
5
|
-
|
6
|
-
@_con = (scheme == :https ?
|
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(
|
34
|
-
resp = (
|
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.
|
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:
|
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/
|
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.
|
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
|