plum 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01ad5df7a9751026ab7dfef37ff76b47de82bdb8
4
- data.tar.gz: 7618f58b020da6661e9d5c89974d1fe175d89651
3
+ metadata.gz: 8e0e1f798c885a09557e220ac446f6c0aa048efe
4
+ data.tar.gz: dd31a046c57dd142b85940f4b970e90441d2f490
5
5
  SHA512:
6
- metadata.gz: 11175e66083c4fae1c42301c35ce8b56b2fcf6540dffe68eb890a0a5a56e79e90f34ca744c4c63f20a834abfdba787857c5c906852aae6c9e6ae450af9f58ca6
7
- data.tar.gz: 95ce217f9ed15a9508d67f13903354aed565a58876d15b98a9458c8b400736bfe1def86d9ab091c3ff29902dbbd699986cb08e78216e54419536036216511473
6
+ metadata.gz: 7d19e7d4c6f8b868d6c12824d62848f1532fe34af9dd04bc7128396a51533bbfe6c9c68bb36456c63a90e5fc2a337994d76b89ce0aa32bccb03616edf878a91e
7
+ data.tar.gz: aae1851fa3169ffb041a7efd3563dceee256e7e2aed653e6a8ffe1d8b3163bb7a32977abe02bee7ef86a5d91e792d555d1dd19bac40b72820549fa11c07d154e
data/bin/plum CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- $LOAD_PATH << File.expand_path("../../lib", __FILE__)
2
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
3
3
  require "plum/rack"
4
4
  require "plum/rack/cli"
5
5
 
@@ -8,3 +8,5 @@ require "plum/rack/dsl"
8
8
  require "plum/rack/listener"
9
9
  require "plum/rack/server"
10
10
  require "plum/rack/session"
11
+ require "plum/rack/legacy_session"
12
+ require "plum/rack/thread_pool"
@@ -45,6 +45,7 @@ module Plum
45
45
  config[:debug] = @options[:debug] unless @options[:debug].nil?
46
46
  config[:server_push] = @options[:server_push] unless @options[:server_push].nil?
47
47
  config[:threaded] = @options[:threaded] unless @options[:threaded].nil?
48
+ config[:threadpool_size] = @options[:threadpool_size] unless @options[:threadpool_size].nil?
48
49
 
49
50
  if @options[:fallback_legacy]
50
51
  h, p = @options[:fallback_legacy].split(":")
@@ -124,6 +125,10 @@ module Plum
124
125
  @options[:threaded] = true
125
126
  end
126
127
 
128
+ o.on "--threadpool-size SIZE", "Set the size of thread pool" do |arg|
129
+ @options[:threadpool_size] = arg.to_i
130
+ end
131
+
127
132
  o.on "--fallback-legacy HOST:PORT", "Fallbacks if the client doesn't support HTTP/2" do |arg|
128
133
  @options[:fallback_legacy] = arg
129
134
  end
@@ -7,7 +7,8 @@ module Plum
7
7
  debug: false,
8
8
  log: nil, # $stdout
9
9
  server_push: true,
10
- threaded: false
10
+ threaded: false,
11
+ threadpool_size: 20,
11
12
  }.freeze
12
13
 
13
14
  def initialize(config = {})
@@ -44,6 +44,10 @@ module Plum
44
44
  @config[:threaded] = !!bool
45
45
  end
46
46
 
47
+ def threadpool_size(int)
48
+ @config[:threadpool_size] = int.to_i
49
+ end
50
+
47
51
  def fallback_legacy(str)
48
52
  h, p = str.split(":")
49
53
  @config[:fallback_legacy_host] = h
@@ -0,0 +1,36 @@
1
+ # -*- frozen-string-literal: true -*-
2
+ using Plum::BinaryString
3
+
4
+ module Plum
5
+ module Rack
6
+ class LegacySession
7
+ def initialize(svc, e, sock)
8
+ @svc = svc
9
+ @e = e
10
+ @sock = sock
11
+ @config = svc.config
12
+ end
13
+
14
+ def run
15
+ if @config[:fallback_legacy_host]
16
+ @logger.info "legacy HTTP: fallbacking to: #{@config[:fallback_legacy_host]}:#{@config[:fallback_legacy_port]}"
17
+ upstream = TCPSocket.open(@config[:fallback_legacy_host], @config[:fallback_legacy_port])
18
+ upstream.write(@e.buf) if @e.buf
19
+ loop do
20
+ ret = IO.select([@sock, upstream])
21
+ ret[0].each { |s|
22
+ a = s.readpartial(65536)
23
+ if s == upstream
24
+ @sock.write(a)
25
+ else
26
+ upstream.write(a)
27
+ end
28
+ }
29
+ end
30
+ end
31
+ ensure
32
+ upstream.close if upstream
33
+ end
34
+ end
35
+ end
36
+ end
@@ -10,6 +10,10 @@ module Plum
10
10
  raise "not implemented"
11
11
  end
12
12
 
13
+ def accept(svc)
14
+ raise "not implemented"
15
+ end
16
+
13
17
  def method_missing(name, *args)
14
18
  @server.__send__(name, *args)
15
19
  end
@@ -24,8 +28,24 @@ module Plum
24
28
  @server.to_io
25
29
  end
26
30
 
27
- def plum(sock)
28
- ::Plum::HTTPServerConnection.new(sock.method(:write))
31
+ def accept(svc)
32
+ sock = @server.accept
33
+ Thread.start {
34
+ begin
35
+ plum = ::Plum::HTTPServerConnection.new(sock.method(:write))
36
+ sess = Session.new(svc, sock, plum)
37
+ sess.run
38
+ rescue Errno::ECONNRESET, EOFError # closed
39
+ rescue ::Plum::LegacyHTTPError => e
40
+ @logger.info "legacy HTTP client: #{e}"
41
+ sess = LegacySession.new(svc, e, sock)
42
+ sess.run
43
+ rescue => e
44
+ svc.log_exception(e)
45
+ ensure
46
+ sock.close
47
+ end
48
+ }
29
49
  end
30
50
  end
31
51
 
@@ -57,7 +77,7 @@ module Plum
57
77
  }
58
78
  tcp_server = ::TCPServer.new(lc[:hostname], lc[:port])
59
79
  @server = OpenSSL::SSL::SSLServer.new(tcp_server, ctx)
60
- @server.start_immediately = false
80
+ @server.start_immediately = false # call socket#accept twice: [tcp, tls]
61
81
  end
62
82
 
63
83
  def parse_chained_cert(str)
@@ -68,9 +88,26 @@ module Plum
68
88
  @server.to_io
69
89
  end
70
90
 
71
- def plum(sock)
72
- raise ::Plum::LegacyHTTPError.new("client didn't offer h2 with ALPN", nil) unless sock.alpn_protocol == "h2"
73
- ::Plum::ServerConnection.new(sock.method(:write))
91
+ def accept(svc)
92
+ sock = @server.accept
93
+ Thread.start {
94
+ begin
95
+ sock = sock.accept
96
+ raise ::Plum::LegacyHTTPError.new("client didn't offer h2 with ALPN", nil) unless sock.alpn_protocol == "h2"
97
+ plum = ::Plum::ServerConnection.new(sock.method(:write))
98
+ sess = Session.new(svc, sock, plum)
99
+ sess.run
100
+ rescue Errno::ECONNRESET, EOFError # closed
101
+ rescue ::Plum::LegacyHTTPError => e
102
+ @logger.info "legacy HTTP client: #{e}"
103
+ sess = LegacySession.new(svc, e, sock)
104
+ sess.run
105
+ rescue => e
106
+ svc.log_exception(e)
107
+ ensure
108
+ sock.close if sock
109
+ end
110
+ }
74
111
  end
75
112
 
76
113
  private
@@ -126,8 +163,20 @@ module Plum
126
163
  @server.to_io
127
164
  end
128
165
 
129
- def plum(sock)
130
- ::Plum::ServerConnection.new(sock.method(:write))
166
+ def accept(svc)
167
+ sock = @server.accept
168
+ Thread.start {
169
+ begin
170
+ plum = ::Plum::ServerConnection.new(sock.method(:write))
171
+ sess = Session.new(svc, sock, plum)
172
+ sess.run
173
+ rescue Errno::ECONNRESET, EOFError # closed
174
+ rescue => e
175
+ svc.log_exception(e)
176
+ ensure
177
+ sock.close if sock
178
+ end
179
+ }
131
180
  end
132
181
  end
133
182
  end
@@ -2,18 +2,15 @@
2
2
  module Plum
3
3
  module Rack
4
4
  class Server
5
- attr_reader :config
5
+ attr_reader :config, :app, :logger, :threadpool
6
6
 
7
7
  def initialize(app, config)
8
8
  @config = config
9
9
  @state = :null
10
10
  @app = config[:debug] ? ::Rack::CommonLogger.new(app) : app
11
- @logger = Logger.new(config[:log] || $stdout).tap { |l|
12
- l.level = config[:debug] ? Logger::DEBUG : Logger::INFO
13
- }
14
- @listeners = config[:listeners].map { |lc|
15
- lc[:listener].new(lc)
16
- }
11
+ @logger = Logger.new(config[:log] || $stdout).tap { |l| l.level = config[:debug] ? Logger::DEBUG : Logger::INFO }
12
+ @listeners = config[:listeners].map { |lc| lc[:listener].new(lc) }
13
+ @threadpool = ThreadPool.new(@config[:threadpool_size]) if @config[:threaded]
17
14
 
18
15
  @logger.info("Plum #{::Plum::VERSION}")
19
16
  @logger.info("Config: #{config}")
@@ -24,87 +21,41 @@ module Plum
24
21
  end
25
22
 
26
23
  def start
24
+ #trap(:INT) { @state = :ee }
25
+ #require "lineprof"
26
+ #Lineprof.profile(//){
27
27
  @state = :running
28
- while @state == :running
29
- break if @listeners.empty?
28
+ while @state == :running && !@listeners.empty?
30
29
  begin
31
30
  if ss = IO.select(@listeners, nil, nil, 2.0)
32
31
  ss[0].each { |svr|
33
- new_con(svr)
32
+ begin
33
+ svr.accept(self)
34
+ rescue Errno::ECONNRESET, Errno::ECONNABORTED # closed
35
+ rescue => e
36
+ log_exception(e)
37
+ end
34
38
  }
35
39
  end
36
- rescue Errno::EBADF, Errno::ENOTSOCK, IOError => e # closed
37
- rescue StandardError => e
40
+ rescue Errno::EBADF # closed
41
+ rescue => e
38
42
  log_exception(e)
39
43
  end
40
44
  end
45
+ #}
41
46
  end
42
47
 
43
48
  def stop
44
49
  @state = :stop
45
50
  @listeners.map(&:stop)
46
- # TODO: gracefully shutdown connections
51
+ # TODO: gracefully shutdown connections (wait threadpool?)
47
52
  end
48
53
 
49
54
  private
50
- def new_con(svr)
51
- sock = svr.accept
52
- Thread.new {
53
- begin
54
- begin
55
- sock = sock.accept if sock.respond_to?(:accept)
56
- plum = svr.plum(sock)
57
-
58
- con = Session.new(app: @app,
59
- plum: plum,
60
- sock: sock,
61
- logger: @logger,
62
- config: @config,
63
- remote_addr: sock.peeraddr.last)
64
- con.run
65
- rescue ::Plum::LegacyHTTPError => e
66
- @logger.info "legacy HTTP client: #{e}"
67
- handle_legacy(e, sock)
68
- end
69
- rescue Errno::ECONNRESET, Errno::EPROTO, Errno::EINVAL, EOFError => e # closed
70
- rescue StandardError => e
71
- log_exception(e)
72
- ensure
73
- sock.close if sock
74
- end
75
- }
76
- rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL => e # closed
77
- sock.close if sock
78
- rescue StandardError => e
79
- log_exception(e)
80
- sock.close if sock
81
- end
82
-
83
55
  def log_exception(e)
84
56
  @logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}")
85
57
  end
86
58
 
87
- def handle_legacy(e, sock)
88
- if @config[:fallback_legacy_host]
89
- @logger.info "legacy HTTP: fallbacking to: #{@config[:fallback_legacy_host]}:#{@config[:fallback_legacy_port]}"
90
- upstream = TCPSocket.open(@config[:fallback_legacy_host], @config[:fallback_legacy_port])
91
- upstream.write(e.buf) if e.buf
92
- loop do
93
- ret = IO.select([sock, upstream])
94
- ret[0].each { |s|
95
- a = s.readpartial(65536)
96
- if s == upstream
97
- sock.write(a)
98
- else
99
- upstream.write(a)
100
- end
101
- }
102
- end
103
- end
104
- ensure
105
- upstream.close if upstream
106
- end
107
-
108
59
  def drop_privileges
109
60
  begin
110
61
  user = @config[:user]
@@ -8,14 +8,15 @@ module Plum
8
8
  class Session
9
9
  attr_reader :app, :plum
10
10
 
11
- def initialize(app:, plum:, sock:, logger:, config:, remote_addr: "127.0.0.1")
12
- @app = app
13
- @plum = plum
11
+ def initialize(svc, sock, plum)
12
+ @svc = svc
13
+ @app = svc.app
14
14
  @sock = sock
15
- @logger = logger
16
- @config = config
17
- @remote_addr = remote_addr
18
- @request_thread = {} # used if threaded
15
+ @plum = plum
16
+ @logger = svc.logger
17
+ @config = svc.config
18
+ @remote_addr = sock.peeraddr.last
19
+ @threadpool = svc.threadpool
19
20
 
20
21
  setup_plum
21
22
  end
@@ -24,12 +25,15 @@ module Plum
24
25
  @plum.close
25
26
  end
26
27
 
28
+ def to_io
29
+ @sock.to_io
30
+ end
31
+
27
32
  def run
28
33
  while !@sock.closed? && !@sock.eof?
29
34
  @plum << @sock.readpartial(1024)
30
35
  end
31
36
  ensure
32
- @request_thread.each { |stream, thread| thread.kill }
33
37
  stop
34
38
  end
35
39
 
@@ -57,12 +61,21 @@ module Plum
57
61
  }
58
62
 
59
63
  @plum.on(:end_stream) { |stream|
60
- if @config[:threaded]
61
- @request_thread[stream] = Thread.new {
62
- handle_request(stream, reqs[stream][:headers], reqs[stream][:data])
64
+ req = reqs.delete(stream)
65
+ err = proc { |err|
66
+ stream.send_headers({ ":status" => 500 }, end_stream: true)
67
+ @logger.error(err)
68
+ }
69
+ if @threadpool
70
+ @threadpool.acquire(err) {
71
+ handle_request(stream, req[:headers], req[:data])
63
72
  }
64
73
  else
65
- handle_request(stream, reqs[stream][:headers], reqs[stream][:data])
74
+ begin
75
+ handle_request(stream, req[:headers], req[:data])
76
+ rescue
77
+ err.call($!)
78
+ end
66
79
  end
67
80
  }
68
81
  end
@@ -138,8 +151,6 @@ module Plum
138
151
  send_body(st, p_body) unless pno_body
139
152
  }
140
153
  end
141
-
142
- @request_thread.delete(stream)
143
154
  end
144
155
 
145
156
  def new_env(h, data)
@@ -0,0 +1,35 @@
1
+ # -*- frozen-string-literal: true -*-
2
+ module Plum
3
+ module Rack
4
+ class ThreadPool
5
+ def initialize(size = 20)
6
+ @workers = Set.new
7
+ @jobs = Queue.new
8
+
9
+ size.times { |i|
10
+ spawn_worker
11
+ }
12
+ end
13
+
14
+ # returns cancel token
15
+ def acquire(tag = nil, err = nil, &blk)
16
+ @jobs << [blk, err]
17
+ end
18
+
19
+ private
20
+ def spawn_worker
21
+ t = Thread.new {
22
+ while true
23
+ job, err = @jobs.pop
24
+ begin
25
+ job.call
26
+ rescue => e
27
+ err << e if err
28
+ end
29
+ end
30
+ }
31
+ @workers << t
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,4 @@
1
1
  # -*- frozen-string-literal: true -*-
2
2
  module Plum
3
- VERSION = "0.2.8"
3
+ VERSION = "0.2.9"
4
4
  end
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.8
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - rhenium
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-03 00:00:00.000000000 Z
11
+ date: 2016-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -200,9 +200,11 @@ files:
200
200
  - lib/plum/rack/cli.rb
201
201
  - lib/plum/rack/config.rb
202
202
  - lib/plum/rack/dsl.rb
203
+ - lib/plum/rack/legacy_session.rb
203
204
  - lib/plum/rack/listener.rb
204
205
  - lib/plum/rack/server.rb
205
206
  - lib/plum/rack/session.rb
207
+ - lib/plum/rack/thread_pool.rb
206
208
  - lib/plum/server/connection.rb
207
209
  - lib/plum/server/http_connection.rb
208
210
  - lib/plum/server/ssl_socket_connection.rb