plum 0.0.3 → 0.1.0
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/Gemfile +1 -1
- data/README.md +35 -6
- data/bin/plum +7 -0
- data/examples/rack.ru +22 -0
- data/lib/plum/binary_string.rb +5 -5
- data/lib/plum/connection.rb +24 -25
- data/lib/plum/connection_utils.rb +1 -4
- data/lib/plum/errors.rb +1 -1
- data/lib/plum/event_emitter.rb +7 -5
- data/lib/plum/flow_control.rb +19 -12
- data/lib/plum/frame.rb +60 -50
- data/lib/plum/frame_factory.rb +39 -5
- data/lib/plum/frame_utils.rb +0 -3
- data/lib/plum/hpack/constants.rb +49 -49
- data/lib/plum/hpack/context.rb +4 -3
- data/lib/plum/hpack/decoder.rb +55 -45
- data/lib/plum/hpack/encoder.rb +15 -20
- data/lib/plum/http_connection.rb +19 -3
- data/lib/plum/https_connection.rb +21 -5
- data/lib/plum/rack/cli.rb +130 -0
- data/lib/plum/rack/config.rb +28 -0
- data/lib/plum/rack/connection.rb +169 -0
- data/lib/plum/rack/dsl.rb +44 -0
- data/lib/plum/rack/listener.rb +122 -0
- data/lib/plum/rack/server.rb +68 -0
- data/lib/plum/rack.rb +10 -0
- data/lib/plum/stream.rb +35 -25
- data/lib/plum/stream_utils.rb +0 -4
- data/lib/plum/version.rb +1 -1
- data/lib/plum.rb +1 -0
- data/lib/rack/handler/plum.rb +49 -0
- data/plum.gemspec +1 -0
- data/test/plum/hpack/test_decoder.rb +8 -8
- data/test/plum/hpack/test_encoder.rb +3 -3
- data/test/plum/stream/test_handle_frame.rb +6 -6
- data/test/plum/test_frame.rb +4 -1
- data/test/plum/test_http_connection.rb +1 -3
- data/test/plum/test_https_connection.rb +9 -12
- data/test/utils/server.rb +3 -3
- metadata +28 -3
@@ -0,0 +1,130 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "rack/builder"
|
3
|
+
|
4
|
+
module Plum
|
5
|
+
module Rack
|
6
|
+
# CLI runner. Parses command line options and start ::Plum::Rack::Server.
|
7
|
+
class CLI
|
8
|
+
# Creates new CLI runner and parses command line.
|
9
|
+
#
|
10
|
+
# @param argv [Array<String>] ARGV
|
11
|
+
def initialize(argv)
|
12
|
+
@argv = argv
|
13
|
+
@options = {}
|
14
|
+
|
15
|
+
parse!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Starts ::Plum::Rack::Server
|
19
|
+
def run
|
20
|
+
@server.start
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def parse!
|
25
|
+
@parser = setup_parser
|
26
|
+
@parser.parse!(@argv)
|
27
|
+
|
28
|
+
config = transform_options
|
29
|
+
# TODO: parse rack_opts?
|
30
|
+
rack_app, rack_opts = ::Rack::Builder.parse_file(@argv.shift || "config.ru")
|
31
|
+
|
32
|
+
@server = Plum::Rack::Server.new(rack_app, config)
|
33
|
+
end
|
34
|
+
|
35
|
+
def transform_options
|
36
|
+
if @options[:config]
|
37
|
+
dsl = DSL::Config.new.instance_eval(File.read(@options[:config]))
|
38
|
+
config = dsl.config
|
39
|
+
else
|
40
|
+
config = Config.new
|
41
|
+
end
|
42
|
+
|
43
|
+
ENV["RACK_ENV"] = @options[:env] if @options[:env]
|
44
|
+
config[:debug] = @options[:debug] unless @options[:debug].nil?
|
45
|
+
config[:server_push] = @options[:server_push] unless @options[:server_push].nil?
|
46
|
+
|
47
|
+
if @options[:socket]
|
48
|
+
config[:listeners] << { listener: UNIXListener,
|
49
|
+
path: @options[:socket] }
|
50
|
+
end
|
51
|
+
|
52
|
+
if !@options[:socket] || @options[:host] || @options[:port]
|
53
|
+
if @options[:tls] == false
|
54
|
+
config[:listeners] << { listener: TCPListener,
|
55
|
+
hostname: @options[:host] || "0.0.0.0",
|
56
|
+
port: @options[:port] || 8080 }
|
57
|
+
else
|
58
|
+
config[:listeners] << { listener: TLSListener,
|
59
|
+
hostname: @options[:host] || "0.0.0.0",
|
60
|
+
port: @options[:port] || 8080,
|
61
|
+
certificate: @options[:cert] && File.read(@options[:cert]),
|
62
|
+
certificate_key: @options[:cert] && File.read(@options[:key]) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
config
|
67
|
+
end
|
68
|
+
|
69
|
+
def setup_parser
|
70
|
+
parser = OptionParser.new do |o|
|
71
|
+
o.on "-C", "--config PATH", "Load PATH as a config" do |arg|
|
72
|
+
@options[:config] = arg
|
73
|
+
end
|
74
|
+
|
75
|
+
o.on "-D", "--debug", "Run puma in debug mode" do
|
76
|
+
@options[:debug] = true
|
77
|
+
end
|
78
|
+
|
79
|
+
o.on "-e", "--environment ENV", "Rack environment (default: development)" do |arg|
|
80
|
+
@options[:env] = arg
|
81
|
+
end
|
82
|
+
|
83
|
+
o.on "-a", "--address HOST", "Bind to host HOST (default: 0.0.0.0)" do |arg|
|
84
|
+
@options[:host] = arg
|
85
|
+
end
|
86
|
+
|
87
|
+
o.on "-p", "--port PORT", "Bind to port PORT (default: 8080)" do |arg|
|
88
|
+
@options[:port] = arg.to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
o.on "-S", "--socket PATH", "Bind to UNIX domain socket" do |arg|
|
92
|
+
@options[:socket] = arg
|
93
|
+
end
|
94
|
+
|
95
|
+
o.on "--http", "Use http URI scheme (use raw TCP)" do |arg|
|
96
|
+
@options[:tls] = false
|
97
|
+
end
|
98
|
+
|
99
|
+
o.on "--https", "Use https URI scheme (use TLS; default)" do |arg|
|
100
|
+
@options[:tls] = true
|
101
|
+
end
|
102
|
+
|
103
|
+
o.on "--server-push BOOL", "Enable HTTP/2 server push" do |arg|
|
104
|
+
@options[:server_push] = arg != "false"
|
105
|
+
end
|
106
|
+
|
107
|
+
o.on "--cert PATH", "Use PATH as server certificate" do |arg|
|
108
|
+
@options[:cert] = arg
|
109
|
+
end
|
110
|
+
|
111
|
+
o.on "--key PATH", "Use PATH as server certificate's private key" do |arg|
|
112
|
+
@options[:key] = arg
|
113
|
+
end
|
114
|
+
|
115
|
+
o.on "-v", "--version", "Show version" do
|
116
|
+
puts "plum version #{::Plum::VERSION}"
|
117
|
+
exit(0)
|
118
|
+
end
|
119
|
+
|
120
|
+
o.on "-h", "--help", "Show this message" do
|
121
|
+
puts o
|
122
|
+
exit(0)
|
123
|
+
end
|
124
|
+
|
125
|
+
o.banner = "plum [options] [rackup config file]"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Plum
|
2
|
+
module Rack
|
3
|
+
class Config
|
4
|
+
DEFAULT_CONFIG = {
|
5
|
+
listeners: [],
|
6
|
+
debug: false,
|
7
|
+
log: nil, # $stdout
|
8
|
+
server_push: true
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def initialize(config = {})
|
12
|
+
@config = DEFAULT_CONFIG.merge(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@config[key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(key, value)
|
20
|
+
@config[key] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@config.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Plum
|
2
|
+
module Rack
|
3
|
+
class Connection
|
4
|
+
attr_reader :app, :plum
|
5
|
+
|
6
|
+
def initialize(app, plum, logger)
|
7
|
+
@app = app
|
8
|
+
@plum = plum
|
9
|
+
@logger = logger
|
10
|
+
|
11
|
+
setup_plum
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop
|
15
|
+
@plum.close
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
begin
|
20
|
+
@plum.run
|
21
|
+
rescue Errno::EPIPE, Errno::ECONNRESET => e
|
22
|
+
rescue StandardError => e
|
23
|
+
@logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def setup_plum
|
29
|
+
@plum.on(:connection_error) { |ex| @logger.error(ex) }
|
30
|
+
|
31
|
+
# @plum.on(:stream) { |stream| @logger.debug("new stream: #{stream}") }
|
32
|
+
@plum.on(:stream_error) { |stream, ex| @logger.error(ex) }
|
33
|
+
|
34
|
+
reqs = {}
|
35
|
+
@plum.on(:headers) { |stream, h|
|
36
|
+
reqs[stream] = { headers: h, data: "".force_encoding(Encoding::BINARY) }
|
37
|
+
}
|
38
|
+
|
39
|
+
@plum.on(:data) { |stream, d|
|
40
|
+
reqs[stream][:data] << d # TODO: store to file?
|
41
|
+
}
|
42
|
+
|
43
|
+
@plum.on(:end_stream) { |stream|
|
44
|
+
handle_request(stream, reqs[stream][:headers], reqs[stream][:data])
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_body(stream, body)
|
49
|
+
begin
|
50
|
+
if body.is_a?(Array)
|
51
|
+
last = body.size - 1
|
52
|
+
body.each_with_index { |part, i|
|
53
|
+
stream.send_data(part, end_stream: last == i)
|
54
|
+
}
|
55
|
+
elsif body.is_a?(IO)
|
56
|
+
stream.send_data(body, end_stream: true)
|
57
|
+
else
|
58
|
+
body.each { |part| stream.send_data(part, end_stream: false) }
|
59
|
+
stream.send_data(nil, end_stream: true)
|
60
|
+
end
|
61
|
+
ensure
|
62
|
+
body.close if body.respond_to?(:close)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def extract_push(r_extheaders)
|
67
|
+
if pushs = r_extheaders["plum.serverpush"]
|
68
|
+
pushs.split(";").map { |push| push.split(" ", 2) }
|
69
|
+
else
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_request(stream, headers, data)
|
75
|
+
env = new_env(headers, data)
|
76
|
+
r_status, r_rawheaders, r_body = @app.call(env)
|
77
|
+
r_headers, r_extheaders = extract_headers(r_status, r_rawheaders)
|
78
|
+
r_topushs = extract_push(r_extheaders)
|
79
|
+
|
80
|
+
stream.send_headers(r_headers, end_stream: false)
|
81
|
+
r_pushstreams = r_topushs.map { |method, path|
|
82
|
+
preq = { ":authority" => headers.find { |k, v| k == ":authority" }[1],
|
83
|
+
":method" => method.to_s.upcase,
|
84
|
+
":scheme" => headers.find { |k, v| k == ":scheme" }[1],
|
85
|
+
":path" => path }
|
86
|
+
st = stream.promise(preq)
|
87
|
+
[st, preq]
|
88
|
+
}
|
89
|
+
|
90
|
+
send_body(stream, r_body)
|
91
|
+
|
92
|
+
r_pushstreams.each { |st, preq|
|
93
|
+
penv = new_env(preq, "")
|
94
|
+
p_status, p_h, p_body = @app.call(penv)
|
95
|
+
p_headers = extract_headers(p_status, p_h)
|
96
|
+
st.send_headers(p_headers, end_stream: false)
|
97
|
+
send_body(st, p_body)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def new_env(h, data)
|
102
|
+
ebase = {
|
103
|
+
"SCRIPT_NAME" => "",
|
104
|
+
"rack.version" => ::Rack::VERSION,
|
105
|
+
"rack.input" => StringIO.new(data),
|
106
|
+
"rack.errors" => $stderr,
|
107
|
+
"rack.multithread" => true,
|
108
|
+
"rack.multiprocess" => false,
|
109
|
+
"rack.run_once" => false,
|
110
|
+
"rack.hijack?" => false,
|
111
|
+
}
|
112
|
+
|
113
|
+
h.each { |k, v|
|
114
|
+
case k
|
115
|
+
when ":method"
|
116
|
+
ebase["REQUEST_METHOD"] = v
|
117
|
+
when ":path"
|
118
|
+
cpath_name, cpath_query = v.split("?", 2)
|
119
|
+
ebase["PATH_INFO"] = cpath_name
|
120
|
+
ebase["QUERY_STRING"] = cpath_query || ""
|
121
|
+
when ":authority"
|
122
|
+
chost, cport = v.split(":", 2)
|
123
|
+
ebase["SERVER_NAME"] = chost
|
124
|
+
ebase["SERVER_PORT"] = (cport || 443).to_i
|
125
|
+
when ":scheme"
|
126
|
+
ebase["rack.url_scheme"] = v
|
127
|
+
else
|
128
|
+
if k.start_with?(":")
|
129
|
+
# unknown HTTP/2 pseudo-headers
|
130
|
+
else
|
131
|
+
if "cookie" == k && ebase["HTTP_COOKIE"]
|
132
|
+
ebase["HTTP_COOKIE"] << "; " << v
|
133
|
+
else
|
134
|
+
ebase["HTTP_" << k.tr("-", "_").upcase!] = v
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
}
|
139
|
+
|
140
|
+
ebase
|
141
|
+
end
|
142
|
+
|
143
|
+
def extract_headers(r_status, r_h)
|
144
|
+
rbase = {
|
145
|
+
":status" => r_status,
|
146
|
+
"server" => "plum/#{::Plum::VERSION}",
|
147
|
+
}
|
148
|
+
rext = {}
|
149
|
+
|
150
|
+
r_h.each do |key, v_|
|
151
|
+
if key.include?(".")
|
152
|
+
rext[key] = v_
|
153
|
+
else
|
154
|
+
key = key.downcase
|
155
|
+
|
156
|
+
if "set-cookie" == key
|
157
|
+
rbase[key] = v_.gsub("\n", "; ") # RFC 7540 8.1.2.5
|
158
|
+
else
|
159
|
+
key = key.byteshift(2) if key.start_with?("x-")
|
160
|
+
rbase[key] = v_.tr("\n", ",") # RFC 7230 7
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
[rbase, rext]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Plum
|
2
|
+
module Rack
|
3
|
+
module DSL
|
4
|
+
class Config
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@config = ::Plum::Rack::Config::DEFAULT_CONFIG.dup
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(out)
|
12
|
+
if out.is_a?(String)
|
13
|
+
@config[:log] = File.open(out, "a")
|
14
|
+
else
|
15
|
+
@config[:log] = out
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def debug(bool)
|
20
|
+
@config[:debug] = !!bool
|
21
|
+
end
|
22
|
+
|
23
|
+
def listener(type, conf)
|
24
|
+
case type
|
25
|
+
when :unix
|
26
|
+
lc = conf.merge(listener: UNIXListener)
|
27
|
+
when :tcp
|
28
|
+
lc = conf.merge(listener: TCPListener)
|
29
|
+
when :tls
|
30
|
+
lc = conf.merge(listener: TLSListener)
|
31
|
+
else
|
32
|
+
raise "Unknown listener type: #{type} (known type: :unix, :http, :https)"
|
33
|
+
end
|
34
|
+
|
35
|
+
@config[:listeners] << lc
|
36
|
+
end
|
37
|
+
|
38
|
+
def server_push(bool)
|
39
|
+
@config[:server_push] = !!bool
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Plum
|
2
|
+
module Rack
|
3
|
+
class BaseListener
|
4
|
+
def stop
|
5
|
+
@server.close
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_io
|
9
|
+
raise "not implemented"
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args)
|
13
|
+
@server.__send__(name, *args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TCPListener < BaseListener
|
18
|
+
def initialize(lc)
|
19
|
+
@server = ::TCPServer.new(lc[:hostname], lc[:port])
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_io
|
23
|
+
@server.to_io
|
24
|
+
end
|
25
|
+
|
26
|
+
def plum(sock)
|
27
|
+
::Plum::HTTPConnection.new(sock)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class TLSListener < BaseListener
|
32
|
+
def initialize(lc)
|
33
|
+
cert, key = lc[:certificate], lc[:certificate_key]
|
34
|
+
unless cert && key
|
35
|
+
puts "WARNING: using dummy certificate"
|
36
|
+
cert, key = dummy_key
|
37
|
+
end
|
38
|
+
|
39
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
40
|
+
ctx.ssl_version = :TLSv1_2
|
41
|
+
ctx.alpn_select_cb = -> protocols {
|
42
|
+
raise "Client does not support HTTP/2: #{protocols}" unless protocols.include?("h2")
|
43
|
+
"h2"
|
44
|
+
}
|
45
|
+
ctx.tmp_ecdh_callback = -> (sock, ise, keyl) { OpenSSL::PKey::EC.new("prime256v1") }
|
46
|
+
ctx.cert = OpenSSL::X509::Certificate.new(cert)
|
47
|
+
ctx.key = OpenSSL::PKey::RSA.new(key)
|
48
|
+
tcp_server = ::TCPServer.new(lc[:hostname], lc[:port])
|
49
|
+
@server = OpenSSL::SSL::SSLServer.new(tcp_server, ctx)
|
50
|
+
@server.start_immediately = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_io
|
54
|
+
@server.to_io
|
55
|
+
end
|
56
|
+
|
57
|
+
def plum(sock)
|
58
|
+
::Plum::HTTPSConnection.new(sock)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
# returns: [cert, key]
|
63
|
+
def dummy_key
|
64
|
+
puts "WARNING: Generating new dummy certificate..."
|
65
|
+
|
66
|
+
key = OpenSSL::PKey::RSA.new(2048)
|
67
|
+
cert = OpenSSL::X509::Certificate.new
|
68
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=JP/O=Test/OU=Test/CN=example.com")
|
69
|
+
cert.not_before = Time.now
|
70
|
+
cert.not_after = Time.now + 363 * 24 * 60 * 60
|
71
|
+
cert.public_key = key.public_key
|
72
|
+
cert.serial = rand((1 << 20) - 1)
|
73
|
+
cert.version = 2
|
74
|
+
|
75
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
76
|
+
ef.subject_certificate = cert
|
77
|
+
ef.issuer_certificate = cert
|
78
|
+
cert.extensions = [
|
79
|
+
ef.create_extension("basicConstraints","CA:TRUE", true),
|
80
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
81
|
+
]
|
82
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
83
|
+
|
84
|
+
cert.sign key, OpenSSL::Digest::SHA1.new
|
85
|
+
|
86
|
+
[cert, key]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class UNIXListener < BaseListener
|
91
|
+
def initialize(lc)
|
92
|
+
if File.exist?(lc[:path])
|
93
|
+
begin
|
94
|
+
old = UNIXSocket.new(lc[:path])
|
95
|
+
rescue SystemCallError, IOError
|
96
|
+
File.unlink(lc[:path])
|
97
|
+
else
|
98
|
+
old.close
|
99
|
+
raise "Already a server bound to: #{lc[:path]}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
@server = ::UNIXServer.new(lc[:path])
|
104
|
+
|
105
|
+
File.chmod(lc[:mode], lc[:path]) if lc[:mode]
|
106
|
+
end
|
107
|
+
|
108
|
+
def stop
|
109
|
+
super
|
110
|
+
File.unlink(lc[:path])
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_io
|
114
|
+
@server.to_io
|
115
|
+
end
|
116
|
+
|
117
|
+
def plum(sock)
|
118
|
+
::Plum::HTTPSConnection.new(sock)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Plum
|
2
|
+
module Rack
|
3
|
+
class Server
|
4
|
+
def initialize(app, config)
|
5
|
+
@state = :null
|
6
|
+
@app = config[:debug] ? ::Rack::CommonLogger.new(app) : app
|
7
|
+
@logger = Logger.new(config[:log] || $stdout).tap { |l|
|
8
|
+
l.level = config[:debug] ? Logger::DEBUG : Logger::INFO
|
9
|
+
}
|
10
|
+
@listeners = config[:listeners].map { |lc|
|
11
|
+
lc[:listener].new(lc)
|
12
|
+
}
|
13
|
+
|
14
|
+
@logger.info("Plum #{::Plum::VERSION}")
|
15
|
+
@logger.info("Config: #{config}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@state = :running
|
20
|
+
while @state == :running
|
21
|
+
break if @listeners.empty?
|
22
|
+
begin
|
23
|
+
if ss = IO.select(@listeners, nil, nil, 2.0)
|
24
|
+
ss[0].each { |svr|
|
25
|
+
new_con(svr)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
rescue Errno::EBADF, Errno::ENOTSOCK, IOError => e # closed
|
29
|
+
rescue StandardError => e
|
30
|
+
log_exception(e)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
@state = :stop
|
37
|
+
@listeners.map(&:stop)
|
38
|
+
# TODO: gracefully shutdown connections
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def new_con(svr)
|
43
|
+
sock = svr.accept
|
44
|
+
Thread.new {
|
45
|
+
begin
|
46
|
+
sock = sock.accept if sock.respond_to?(:accept)
|
47
|
+
plum = svr.plum(sock)
|
48
|
+
|
49
|
+
con = Connection.new(@app, plum, @logger)
|
50
|
+
con.run
|
51
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL => e # closed
|
52
|
+
sock.close if sock
|
53
|
+
rescue StandardError => e
|
54
|
+
log_exception(e)
|
55
|
+
sock.close if sock
|
56
|
+
end
|
57
|
+
}
|
58
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL => e # closed
|
59
|
+
rescue StandardError => e
|
60
|
+
log_exception(e)
|
61
|
+
end
|
62
|
+
|
63
|
+
def log_exception(e)
|
64
|
+
@logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/plum/rack.rb
ADDED