plum 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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