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 +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
|
[![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
|
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
|