wendell-puma 2.9.2

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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +55 -0
  3. data/DEPLOYMENT.md +92 -0
  4. data/Gemfile +17 -0
  5. data/History.txt +588 -0
  6. data/LICENSE +26 -0
  7. data/Manifest.txt +68 -0
  8. data/README.md +251 -0
  9. data/Rakefile +158 -0
  10. data/bin/puma +10 -0
  11. data/bin/puma-wild +31 -0
  12. data/bin/pumactl +12 -0
  13. data/docs/config.md +0 -0
  14. data/docs/nginx.md +80 -0
  15. data/docs/signals.md +43 -0
  16. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  17. data/ext/puma_http11/ext_help.h +15 -0
  18. data/ext/puma_http11/extconf.rb +9 -0
  19. data/ext/puma_http11/http11_parser.c +1225 -0
  20. data/ext/puma_http11/http11_parser.h +64 -0
  21. data/ext/puma_http11/http11_parser.java.rl +161 -0
  22. data/ext/puma_http11/http11_parser.rl +146 -0
  23. data/ext/puma_http11/http11_parser_common.rl +54 -0
  24. data/ext/puma_http11/io_buffer.c +155 -0
  25. data/ext/puma_http11/mini_ssl.c +198 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
  28. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +391 -0
  29. data/ext/puma_http11/puma_http11.c +491 -0
  30. data/lib/puma.rb +14 -0
  31. data/lib/puma/accept_nonblock.rb +23 -0
  32. data/lib/puma/app/status.rb +59 -0
  33. data/lib/puma/binder.rb +298 -0
  34. data/lib/puma/capistrano.rb +86 -0
  35. data/lib/puma/cli.rb +606 -0
  36. data/lib/puma/client.rb +289 -0
  37. data/lib/puma/cluster.rb +404 -0
  38. data/lib/puma/compat.rb +18 -0
  39. data/lib/puma/configuration.rb +377 -0
  40. data/lib/puma/const.rb +165 -0
  41. data/lib/puma/control_cli.rb +251 -0
  42. data/lib/puma/daemon_ext.rb +25 -0
  43. data/lib/puma/delegation.rb +11 -0
  44. data/lib/puma/detect.rb +4 -0
  45. data/lib/puma/events.rb +130 -0
  46. data/lib/puma/io_buffer.rb +7 -0
  47. data/lib/puma/java_io_buffer.rb +45 -0
  48. data/lib/puma/jruby_restart.rb +83 -0
  49. data/lib/puma/minissl.rb +187 -0
  50. data/lib/puma/null_io.rb +34 -0
  51. data/lib/puma/rack_default.rb +7 -0
  52. data/lib/puma/rack_patch.rb +45 -0
  53. data/lib/puma/reactor.rb +183 -0
  54. data/lib/puma/runner.rb +146 -0
  55. data/lib/puma/server.rb +801 -0
  56. data/lib/puma/single.rb +102 -0
  57. data/lib/puma/tcp_logger.rb +32 -0
  58. data/lib/puma/thread_pool.rb +185 -0
  59. data/lib/puma/util.rb +9 -0
  60. data/lib/rack/handler/puma.rb +66 -0
  61. data/test/test_app_status.rb +92 -0
  62. data/test/test_cli.rb +173 -0
  63. data/test/test_config.rb +26 -0
  64. data/test/test_http10.rb +27 -0
  65. data/test/test_http11.rb +144 -0
  66. data/test/test_integration.rb +165 -0
  67. data/test/test_iobuffer.rb +38 -0
  68. data/test/test_minissl.rb +29 -0
  69. data/test/test_null_io.rb +31 -0
  70. data/test/test_persistent.rb +238 -0
  71. data/test/test_puma_server.rb +288 -0
  72. data/test/test_puma_server_ssl.rb +137 -0
  73. data/test/test_rack_handler.rb +10 -0
  74. data/test/test_rack_server.rb +141 -0
  75. data/test/test_tcp_rack.rb +42 -0
  76. data/test/test_thread_pool.rb +156 -0
  77. data/test/test_unix_socket.rb +39 -0
  78. data/test/test_ws.rb +89 -0
  79. data/tools/jungle/README.md +9 -0
  80. data/tools/jungle/init.d/README.md +54 -0
  81. data/tools/jungle/init.d/puma +332 -0
  82. data/tools/jungle/init.d/run-puma +3 -0
  83. data/tools/jungle/upstart/README.md +61 -0
  84. data/tools/jungle/upstart/puma-manager.conf +31 -0
  85. data/tools/jungle/upstart/puma.conf +63 -0
  86. data/tools/trickletest.rb +45 -0
  87. data/wendell-puma.gemspec +55 -0
  88. metadata +225 -0
@@ -0,0 +1,83 @@
1
+ require 'ffi'
2
+
3
+ module Puma
4
+ module JRubyRestart
5
+ extend FFI::Library
6
+ ffi_lib 'c'
7
+
8
+ attach_function :execlp, [:string, :varargs], :int
9
+ attach_function :chdir, [:string], :int
10
+ attach_function :fork, [], :int
11
+ attach_function :exit, [:int], :void
12
+ attach_function :setsid, [], :int
13
+
14
+ def self.chdir_exec(dir, argv)
15
+ chdir(dir)
16
+ cmd = argv.first
17
+ argv = ([:string] * argv.size).zip(argv).flatten
18
+ argv << :string
19
+ argv << nil
20
+ execlp(cmd, *argv)
21
+ raise SystemCallError.new(FFI.errno)
22
+ end
23
+
24
+ PermKey = 'PUMA_DAEMON_PERM'
25
+ RestartKey = 'PUMA_DAEMON_RESTART'
26
+
27
+ # Called to tell things "Your now always in daemon mode,
28
+ # don't try to reenter it."
29
+ #
30
+ def self.perm_daemonize
31
+ ENV[PermKey] = "1"
32
+ end
33
+
34
+ def self.daemon?
35
+ ENV.key?(PermKey) || ENV.key?(RestartKey)
36
+ end
37
+
38
+ def self.daemon_init
39
+ return true if ENV.key?(PermKey)
40
+
41
+ return false unless ENV.key? RestartKey
42
+
43
+ master = ENV[RestartKey]
44
+
45
+ # In case the master disappears early
46
+ begin
47
+ Process.kill "SIGUSR2", master.to_i
48
+ rescue SystemCallError => e
49
+ end
50
+
51
+ ENV[RestartKey] = ""
52
+
53
+ setsid
54
+
55
+ null = File.open "/dev/null", "w+"
56
+ STDIN.reopen null
57
+ STDOUT.reopen null
58
+ STDERR.reopen null
59
+
60
+ true
61
+ end
62
+
63
+ def self.daemon_start(dir, argv)
64
+ ENV['PUMA_DAEMON_RESTART'] = Process.pid.to_s
65
+
66
+ if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
67
+ ENV['JRUBY_OPTS'] = k
68
+ end
69
+
70
+ cmd = argv.first
71
+ argv = ([:string] * argv.size).zip(argv).flatten
72
+ argv << :string
73
+ argv << nil
74
+
75
+ chdir(dir)
76
+ ret = fork
77
+ return ret if ret != 0
78
+ execlp(cmd, *argv)
79
+ raise SystemCallError.new(FFI.errno)
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,187 @@
1
+ if Puma::IS_JRUBY
2
+ require 'java'
3
+ java_import 'java.lang.RuntimeException'
4
+ require 'puma/delegation'
5
+ end
6
+
7
+
8
+ module Puma
9
+ module MiniSSL
10
+ class EngineWrapper
11
+ extend Puma::Delegation
12
+
13
+ %w(inject extract write).each do |action|
14
+ forward action, :@engine
15
+ end
16
+
17
+ def initialize(engine)
18
+ @engine=engine
19
+ end
20
+
21
+ if Puma::IS_JRUBY
22
+ def read
23
+ begin
24
+ @engine.read
25
+ rescue RuntimeException=>e
26
+ raise IOError.new("Unable to read from engine, #{e.message}")
27
+ end
28
+ end
29
+ else
30
+ forward :read, :@engine
31
+ end
32
+ end
33
+ class Socket
34
+ def initialize(socket, engine)
35
+ @socket = socket
36
+ @engine = EngineWrapper.new(engine)
37
+ end
38
+
39
+ def to_io
40
+ @socket
41
+ end
42
+
43
+ def readpartial(size)
44
+ while true
45
+ output = @engine.read
46
+ return output if output
47
+
48
+ data = @socket.readpartial(size)
49
+ @engine.inject(data)
50
+ output = @engine.read
51
+
52
+ return output if output
53
+
54
+ while neg_data = @engine.extract
55
+ @socket.write neg_data
56
+ end
57
+ end
58
+ end
59
+
60
+ def engine_read_all
61
+ output = @engine.read
62
+ while output and additional_output = @engine.read
63
+ output << additional_output
64
+ end
65
+ output
66
+ end
67
+
68
+ def read_nonblock(size)
69
+ while true
70
+ output = engine_read_all
71
+ return output if output
72
+
73
+ data = @socket.read_nonblock(size)
74
+
75
+ @engine.inject(data)
76
+ output = engine_read_all
77
+
78
+ return output if output
79
+
80
+ while neg_data = @engine.extract
81
+ @socket.write neg_data
82
+ end
83
+ end
84
+ end
85
+
86
+ def write(data)
87
+ need = data.bytesize
88
+
89
+ while true
90
+ wrote = @engine.write data
91
+ enc = @engine.extract
92
+
93
+ while enc
94
+ @socket.write enc
95
+ enc = @engine.extract
96
+ end
97
+
98
+ need -= wrote
99
+
100
+ return data.bytesize if need == 0
101
+
102
+ data = data[wrote..-1]
103
+ end
104
+ end
105
+
106
+ alias_method :syswrite, :write
107
+
108
+ def flush
109
+ @socket.flush
110
+ end
111
+
112
+ def close
113
+ @socket.close
114
+ end
115
+
116
+ def peeraddr
117
+ @socket.peeraddr
118
+ end
119
+ end
120
+
121
+ class Context
122
+ attr_accessor :verify_mode
123
+
124
+ if defined?(JRUBY_VERSION)
125
+ # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
126
+ attr_reader :keystore
127
+ attr_accessor :keystore_pass
128
+ attr_accessor :enable_SSLv3
129
+
130
+ def initialize
131
+ @enable_SSLv3 = false
132
+ end
133
+
134
+ def keystore=(keystore)
135
+ raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
136
+ @keystore = keystore
137
+ end
138
+ else
139
+ # non-jruby Context properties
140
+ attr_reader :key
141
+ attr_reader :cert
142
+
143
+ def key=(key)
144
+ raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
145
+ @key = key
146
+ end
147
+
148
+ def cert=(cert)
149
+ raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
150
+ @cert = cert
151
+ end
152
+ end
153
+ end
154
+
155
+ VERIFY_NONE = 0
156
+ VERIFY_PEER = 1
157
+
158
+ class Server
159
+ def initialize(socket, ctx)
160
+ @socket = socket
161
+ @ctx = ctx
162
+ end
163
+
164
+ def to_io
165
+ @socket
166
+ end
167
+
168
+ def accept
169
+ io = @socket.accept
170
+ engine = Engine.server @ctx
171
+
172
+ Socket.new io, engine
173
+ end
174
+
175
+ def accept_nonblock
176
+ io = @socket.accept_nonblock
177
+ engine = Engine.server @ctx
178
+
179
+ Socket.new io, engine
180
+ end
181
+
182
+ def close
183
+ @socket.close
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,34 @@
1
+ module Puma
2
+
3
+ # Provides an IO-like object that always appears to contain no data.
4
+ # Used as the value for rack.input when the request has no body.
5
+ #
6
+ class NullIO
7
+ # Always returns nil
8
+ #
9
+ def gets
10
+ nil
11
+ end
12
+
13
+ # Never yields
14
+ #
15
+ def each
16
+ end
17
+
18
+ # Mimics IO#read with no data
19
+ #
20
+ def read(count=nil,buffer=nil)
21
+ (count && count > 0) ? nil : ""
22
+ end
23
+
24
+ # Does nothing
25
+ #
26
+ def rewind
27
+ end
28
+
29
+ # Does nothing
30
+ #
31
+ def close
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ require 'rack/handler/puma'
2
+
3
+ module Rack::Handler
4
+ def self.default(options = {})
5
+ Rack::Handler::Puma
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ require 'rack/commonlogger'
2
+
3
+ module Rack
4
+ # Patch CommonLogger to use after_reply.
5
+ #
6
+ # Simply request this file and CommonLogger will be a bit more
7
+ # efficient.
8
+ class CommonLogger
9
+ remove_method :call
10
+
11
+ def call(env)
12
+ began_at = Time.now
13
+ status, header, body = @app.call(env)
14
+ header = Utils::HeaderHash.new(header)
15
+
16
+ # If we've been hijacked, then output a special line
17
+ if env['rack.hijack_io']
18
+ log_hijacking(env, 'HIJACK', header, began_at)
19
+ elsif ary = env['rack.after_reply']
20
+ ary << lambda { log(env, status, header, began_at) }
21
+ else
22
+ body = BodyProxy.new(body) { log(env, status, header, began_at) }
23
+ end
24
+
25
+ [status, header, body]
26
+ end
27
+
28
+ HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
29
+
30
+ def log_hijacking(env, status, header, began_at)
31
+ now = Time.now
32
+
33
+ logger = @logger || env['rack.errors']
34
+ logger.write HIJACK_FORMAT % [
35
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
36
+ env["REMOTE_USER"] || "-",
37
+ now.strftime("%d/%b/%Y %H:%M:%S"),
38
+ env["REQUEST_METHOD"],
39
+ env["PATH_INFO"],
40
+ env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
41
+ env["HTTP_VERSION"],
42
+ now - began_at ]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,183 @@
1
+ require 'puma/util'
2
+
3
+ module Puma
4
+ class Reactor
5
+ DefaultSleepFor = 5
6
+
7
+ def initialize(server, app_pool)
8
+ @server = server
9
+ @events = server.events
10
+ @app_pool = app_pool
11
+
12
+ @mutex = Mutex.new
13
+ @ready, @trigger = Puma::Util.pipe
14
+ @input = []
15
+ @sleep_for = DefaultSleepFor
16
+ @timeouts = []
17
+
18
+ @sockets = [@ready]
19
+ end
20
+
21
+ private
22
+
23
+ def run_internal
24
+ sockets = @sockets
25
+
26
+ while true
27
+ begin
28
+ ready = IO.select sockets, nil, nil, @sleep_for
29
+ rescue IOError => e
30
+ if sockets.any? { |socket| socket.closed? }
31
+ STDERR.puts "Error in select: #{e.message} (#{e.class})"
32
+ STDERR.puts e.backtrace
33
+ sockets = sockets.reject { |socket| socket.closed? }
34
+ retry
35
+ else
36
+ raise
37
+ end
38
+ end
39
+
40
+ if ready and reads = ready[0]
41
+ reads.each do |c|
42
+ if c == @ready
43
+ @mutex.synchronize do
44
+ case @ready.read(1)
45
+ when "*"
46
+ sockets += @input
47
+ @input.clear
48
+ when "c"
49
+ sockets.delete_if do |s|
50
+ if s == @ready
51
+ false
52
+ else
53
+ s.close
54
+ true
55
+ end
56
+ end
57
+ when "!"
58
+ return
59
+ end
60
+ end
61
+ else
62
+ # We have to be sure to remove it from the timeout
63
+ # list or we'll accidentally close the socket when
64
+ # it's in use!
65
+ if c.timeout_at
66
+ @mutex.synchronize do
67
+ @timeouts.delete c
68
+ end
69
+ end
70
+
71
+ begin
72
+ if c.try_to_finish
73
+ @app_pool << c
74
+ sockets.delete c
75
+ end
76
+
77
+ # The client doesn't know HTTP well
78
+ rescue HttpParserError => e
79
+ c.write_400
80
+ c.close
81
+
82
+ sockets.delete c
83
+
84
+ @events.parse_error @server, c.env, e
85
+ rescue StandardError => e
86
+ c.write_500
87
+ c.close
88
+
89
+ sockets.delete c
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ unless @timeouts.empty?
96
+ @mutex.synchronize do
97
+ now = Time.now
98
+
99
+ while @timeouts.first.timeout_at < now
100
+ c = @timeouts.shift
101
+ c.write_408 if c.in_data_phase
102
+ c.close
103
+ sockets.delete c
104
+
105
+ break if @timeouts.empty?
106
+ end
107
+
108
+ calculate_sleep
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ public
115
+
116
+ def run
117
+ run_internal
118
+ ensure
119
+ @trigger.close
120
+ @ready.close
121
+ end
122
+
123
+ def run_in_thread
124
+ @thread = Thread.new do
125
+ begin
126
+ run_internal
127
+ rescue StandardError => e
128
+ STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
129
+ STDERR.puts e.backtrace
130
+ retry
131
+ ensure
132
+ @trigger.close
133
+ @ready.close
134
+ end
135
+ end
136
+ end
137
+
138
+ def calculate_sleep
139
+ if @timeouts.empty?
140
+ @sleep_for = DefaultSleepFor
141
+ else
142
+ diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
143
+
144
+ if diff < 0.0
145
+ @sleep_for = 0
146
+ else
147
+ @sleep_for = diff
148
+ end
149
+ end
150
+ end
151
+
152
+ def add(c)
153
+ @mutex.synchronize do
154
+ @input << c
155
+ @trigger << "*"
156
+
157
+ if c.timeout_at
158
+ @timeouts << c
159
+ @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
160
+
161
+ calculate_sleep
162
+ end
163
+ end
164
+ end
165
+
166
+ # Close all watched sockets and clear them from being watched
167
+ def clear!
168
+ begin
169
+ @trigger << "c"
170
+ rescue IOError
171
+ end
172
+ end
173
+
174
+ def shutdown
175
+ begin
176
+ @trigger << "!"
177
+ rescue IOError
178
+ end
179
+
180
+ @thread.join
181
+ end
182
+ end
183
+ end