plum 0.2.8 → 0.2.9

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