puma 2.7.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +5 -13
  2. data/DEPLOYMENT.md +91 -0
  3. data/Gemfile +3 -2
  4. data/History.txt +624 -1
  5. data/Manifest.txt +15 -3
  6. data/README.md +129 -14
  7. data/Rakefile +3 -3
  8. data/bin/puma-wild +31 -0
  9. data/bin/pumactl +1 -1
  10. data/docs/nginx.md +1 -1
  11. data/docs/signals.md +43 -0
  12. data/ext/puma_http11/extconf.rb +7 -2
  13. data/ext/puma_http11/http11_parser.java.rl +5 -5
  14. data/ext/puma_http11/io_buffer.c +1 -1
  15. data/ext/puma_http11/mini_ssl.c +233 -18
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
  19. data/ext/puma_http11/puma_http11.c +12 -4
  20. data/lib/puma.rb +1 -0
  21. data/lib/puma/app/status.rb +7 -0
  22. data/lib/puma/binder.rb +108 -39
  23. data/lib/puma/capistrano.rb +23 -6
  24. data/lib/puma/cli.rb +141 -446
  25. data/lib/puma/client.rb +48 -1
  26. data/lib/puma/cluster.rb +207 -58
  27. data/lib/puma/commonlogger.rb +107 -0
  28. data/lib/puma/configuration.rb +262 -235
  29. data/lib/puma/const.rb +97 -14
  30. data/lib/puma/control_cli.rb +85 -77
  31. data/lib/puma/convenient.rb +23 -0
  32. data/lib/puma/daemon_ext.rb +11 -4
  33. data/lib/puma/detect.rb +8 -1
  34. data/lib/puma/dsl.rb +456 -0
  35. data/lib/puma/events.rb +35 -18
  36. data/lib/puma/jruby_restart.rb +1 -1
  37. data/lib/puma/launcher.rb +399 -0
  38. data/lib/puma/minissl.rb +49 -20
  39. data/lib/puma/null_io.rb +15 -0
  40. data/lib/puma/plugin.rb +104 -0
  41. data/lib/puma/plugin/tmp_restart.rb +35 -0
  42. data/lib/puma/rack/backports/uri/common_18.rb +56 -0
  43. data/lib/puma/rack/backports/uri/common_192.rb +52 -0
  44. data/lib/puma/rack/backports/uri/common_193.rb +29 -0
  45. data/lib/puma/rack/builder.rb +295 -0
  46. data/lib/puma/rack/urlmap.rb +90 -0
  47. data/lib/puma/reactor.rb +14 -1
  48. data/lib/puma/runner.rb +35 -17
  49. data/lib/puma/server.rb +161 -58
  50. data/lib/puma/single.rb +15 -10
  51. data/lib/puma/state_file.rb +29 -0
  52. data/lib/puma/thread_pool.rb +88 -13
  53. data/lib/puma/util.rb +123 -0
  54. data/lib/rack/handler/puma.rb +35 -29
  55. data/puma.gemspec +2 -4
  56. data/tools/jungle/init.d/README.md +2 -2
  57. data/tools/jungle/init.d/puma +69 -7
  58. data/tools/jungle/upstart/puma.conf +8 -2
  59. metadata +51 -71
  60. data/COPYING +0 -55
  61. data/TODO +0 -5
  62. data/lib/puma/rack_patch.rb +0 -45
  63. data/test/test_app_status.rb +0 -92
  64. data/test/test_cli.rb +0 -173
  65. data/test/test_config.rb +0 -16
  66. data/test/test_http10.rb +0 -27
  67. data/test/test_http11.rb +0 -145
  68. data/test/test_integration.rb +0 -165
  69. data/test/test_iobuffer.rb +0 -38
  70. data/test/test_minissl.rb +0 -25
  71. data/test/test_null_io.rb +0 -31
  72. data/test/test_persistent.rb +0 -238
  73. data/test/test_puma_server.rb +0 -292
  74. data/test/test_rack_handler.rb +0 -10
  75. data/test/test_rack_server.rb +0 -141
  76. data/test/test_tcp_rack.rb +0 -42
  77. data/test/test_thread_pool.rb +0 -156
  78. data/test/test_unix_socket.rb +0 -39
  79. data/test/test_ws.rb +0 -89
data/lib/puma/minissl.rb CHANGED
@@ -4,6 +4,7 @@ module Puma
4
4
  def initialize(socket, engine)
5
5
  @socket = socket
6
6
  @engine = engine
7
+ @peercert = nil
7
8
  end
8
9
 
9
10
  def to_io
@@ -69,7 +70,7 @@ module Puma
69
70
 
70
71
  return data.bytesize if need == 0
71
72
 
72
- data = data[need..-1]
73
+ data = data[wrote..-1]
73
74
  end
74
75
  end
75
76
 
@@ -86,35 +87,63 @@ module Puma
86
87
  def peeraddr
87
88
  @socket.peeraddr
88
89
  end
90
+
91
+ def peercert
92
+ return @peercert if @peercert
93
+
94
+ raw = @engine.peercert
95
+ return nil unless raw
96
+
97
+ @peercert = OpenSSL::X509::Certificate.new raw
98
+ end
99
+ end
100
+
101
+ if defined?(JRUBY_VERSION)
102
+ class SSLError < StandardError
103
+ # Define this for jruby even though it isn't used.
104
+ end
105
+
106
+ def self.check; end
89
107
  end
90
108
 
91
109
  class Context
92
110
  attr_accessor :verify_mode
93
111
 
94
- attr_reader :key
95
- attr_reader :cert
112
+ if defined?(JRUBY_VERSION)
113
+ # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
114
+ attr_reader :keystore
115
+ attr_accessor :keystore_pass
96
116
 
97
- def key=(key)
98
- raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
99
- @key = key
100
- end
117
+ def keystore=(keystore)
118
+ raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
119
+ @keystore = keystore
120
+ end
121
+ else
122
+ # non-jruby Context properties
123
+ attr_reader :key
124
+ attr_reader :cert
125
+ attr_reader :ca
126
+
127
+ def key=(key)
128
+ raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
129
+ @key = key
130
+ end
101
131
 
102
- def cert=(cert)
103
- raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
104
- @cert = cert
132
+ def cert=(cert)
133
+ raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
134
+ @cert = cert
135
+ end
136
+
137
+ def ca=(ca)
138
+ raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
139
+ @ca = ca
140
+ end
105
141
  end
106
142
  end
107
143
 
108
144
  VERIFY_NONE = 0
109
145
  VERIFY_PEER = 1
110
-
111
- #if defined?(JRUBY_VERSION)
112
- #class Engine
113
- #def self.server(key, cert)
114
- #new(key, cert)
115
- #end
116
- #end
117
- #end
146
+ VERIFY_FAIL_IF_NO_PEER_CERT = 2
118
147
 
119
148
  class Server
120
149
  def initialize(socket, ctx)
@@ -128,14 +157,14 @@ module Puma
128
157
 
129
158
  def accept
130
159
  io = @socket.accept
131
- engine = Engine.server @ctx.key, @ctx.cert
160
+ engine = Engine.server @ctx
132
161
 
133
162
  Socket.new io, engine
134
163
  end
135
164
 
136
165
  def accept_nonblock
137
166
  io = @socket.accept_nonblock
138
- engine = Engine.server @ctx.key, @ctx.cert
167
+ engine = Engine.server @ctx
139
168
 
140
169
  Socket.new io, engine
141
170
  end
data/lib/puma/null_io.rb CHANGED
@@ -30,5 +30,20 @@ module Puma
30
30
  #
31
31
  def close
32
32
  end
33
+
34
+ # Always zero
35
+ #
36
+ def size
37
+ 0
38
+ end
39
+
40
+ def sync=(v)
41
+ end
42
+
43
+ def puts(*ary)
44
+ end
45
+
46
+ def write(*ary)
47
+ end
33
48
  end
34
49
  end
@@ -0,0 +1,104 @@
1
+ module Puma
2
+ class UnknownPlugin < RuntimeError; end
3
+
4
+ class PluginLoader
5
+ def initialize
6
+ @instances = []
7
+ end
8
+
9
+ def create(name)
10
+ if cls = Plugins.find(name)
11
+ plugin = cls.new(Plugin)
12
+ @instances << plugin
13
+ return plugin
14
+ end
15
+
16
+ raise UnknownPlugin, "File failed to register properly named plugin"
17
+ end
18
+
19
+ def fire_starts(launcher)
20
+ @instances.each do |i|
21
+ if i.respond_to? :start
22
+ i.start(launcher)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ class PluginRegistry
29
+ def initialize
30
+ @plugins = {}
31
+ end
32
+
33
+ def register(name, cls)
34
+ @plugins[name] = cls
35
+ end
36
+
37
+ def find(name)
38
+ name = name.to_s
39
+
40
+ if cls = @plugins[name]
41
+ return cls
42
+ end
43
+
44
+ begin
45
+ require "puma/plugin/#{name}"
46
+ rescue LoadError
47
+ raise UnknownPlugin, "Unable to find plugin: #{name}"
48
+ end
49
+
50
+ if cls = @plugins[name]
51
+ return cls
52
+ end
53
+
54
+ raise UnknownPlugin, "file failed to register a plugin"
55
+ end
56
+ end
57
+
58
+ Plugins = PluginRegistry.new
59
+
60
+ class Plugin
61
+ # Matches
62
+ # "C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb:3:in `<top (required)>'"
63
+ # AS
64
+ # C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb
65
+ CALLER_FILE = /
66
+ \A # start of string
67
+ .+ # file path (one or more characters)
68
+ (?= # stop previous match when
69
+ :\d+ # a colon is followed by one or more digits
70
+ :in # followed by a colon followed by in
71
+ )
72
+ /x
73
+
74
+ def self.extract_name(ary)
75
+ path = ary.first[CALLER_FILE]
76
+
77
+ m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
78
+ return m[1]
79
+ end
80
+
81
+ def self.create(&blk)
82
+ name = extract_name(caller)
83
+
84
+ cls = Class.new(self)
85
+
86
+ cls.class_eval(&blk)
87
+
88
+ Plugins.register name, cls
89
+ end
90
+
91
+ def initialize(loader)
92
+ @loader = loader
93
+ end
94
+
95
+ def in_background(&blk)
96
+ Thread.new(&blk)
97
+ end
98
+
99
+ def workers_supported?
100
+ return false if Puma.jruby? || Puma.windows?
101
+ true
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,35 @@
1
+ require 'puma/plugin'
2
+
3
+ Puma::Plugin.create do
4
+ def start(launcher)
5
+ path = File.join("tmp", "restart.txt")
6
+
7
+ orig = nil
8
+
9
+ # If we can't write to the path, then just don't bother with this plugin
10
+ begin
11
+ File.write path, ""
12
+ orig = File.stat(path).mtime
13
+ rescue SystemCallError
14
+ return
15
+ end
16
+
17
+ in_background do
18
+ while true
19
+ sleep 2
20
+
21
+ begin
22
+ mtime = File.stat(path).mtime
23
+ rescue SystemCallError
24
+ # If the file has disappeared, assume that means don't restart
25
+ else
26
+ if mtime > orig
27
+ launcher.restart
28
+ break
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,56 @@
1
+ # :stopdoc:
2
+
3
+ # Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
4
+ #
5
+ # https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
6
+ #
7
+ #
8
+
9
+ module URI
10
+ TBLENCWWWCOMP_ = {} # :nodoc:
11
+ 256.times do |i|
12
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
13
+ end
14
+ TBLENCWWWCOMP_[' '] = '+'
15
+ TBLENCWWWCOMP_.freeze
16
+ TBLDECWWWCOMP_ = {} # :nodoc:
17
+ 256.times do |i|
18
+ h, l = i>>4, i&15
19
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
20
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
21
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
22
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
23
+ end
24
+ TBLDECWWWCOMP_['+'] = ' '
25
+ TBLDECWWWCOMP_.freeze
26
+
27
+ # Encode given +s+ to URL-encoded form data.
28
+ #
29
+ # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
30
+ # (ASCII space) to + and converts others to %XX.
31
+ #
32
+ # This is an implementation of
33
+ # http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
34
+ #
35
+ # See URI.decode_www_form_component, URI.encode_www_form
36
+ def self.encode_www_form_component(s)
37
+ str = s.to_s
38
+ if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
39
+ str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
40
+ '%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
41
+ end.tr(' ', '+')
42
+ else
43
+ str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
44
+ end
45
+ end
46
+
47
+ # Decode given +str+ of URL-encoded form data.
48
+ #
49
+ # This decodes + to SP.
50
+ #
51
+ # See URI.encode_www_form_component, URI.decode_www_form
52
+ def self.decode_www_form_component(str, enc=nil)
53
+ raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
54
+ str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ # :stopdoc:
2
+
3
+ # Stolen from ruby core's uri/common.rb @32618ba to fix DoS issues in 1.9.2
4
+ #
5
+ # https://github.com/ruby/ruby/blob/32618ba7438a2247042bba9b5d85b5d49070f5e5/lib/uri/common.rb
6
+ #
7
+ # Issue:
8
+ # http://redmine.ruby-lang.org/issues/5149
9
+ #
10
+ # Relevant Fixes:
11
+ # https://github.com/ruby/ruby/commit/b5f91deee04aa6ccbe07c23c8222b937c22a799b
12
+ # https://github.com/ruby/ruby/commit/93177c1e5c3906abf14472ae0b905d8b5c72ce1b
13
+ #
14
+ # This should probably be removed once there is a Ruby 1.9.2 patch level that
15
+ # includes this fix.
16
+
17
+ require 'uri/common'
18
+
19
+ module URI
20
+ TBLDECWWWCOMP_ = {} unless const_defined?(:TBLDECWWWCOMP_) #:nodoc:
21
+ if TBLDECWWWCOMP_.empty?
22
+ 256.times do |i|
23
+ h, l = i>>4, i&15
24
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
25
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
26
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
27
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
28
+ end
29
+ TBLDECWWWCOMP_['+'] = ' '
30
+ TBLDECWWWCOMP_.freeze
31
+ end
32
+
33
+ def self.decode_www_form(str, enc=Encoding::UTF_8)
34
+ return [] if str.empty?
35
+ unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
36
+ raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
37
+ end
38
+ ary = []
39
+ $&.scan(/([^=;&]+)=([^;&]*)/) do
40
+ ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
41
+ end
42
+ ary
43
+ end
44
+
45
+ def self.decode_www_form_component(str, enc=Encoding::UTF_8)
46
+ raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
47
+ str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
48
+ end
49
+
50
+ remove_const :WFKV_ if const_defined?(:WFKV_)
51
+ WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
52
+ end
@@ -0,0 +1,29 @@
1
+ # :stopdoc:
2
+
3
+ require 'uri/common'
4
+
5
+ # Issue:
6
+ # http://bugs.ruby-lang.org/issues/5925
7
+ #
8
+ # Relevant commit:
9
+ # https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1
10
+
11
+ module URI
12
+ 256.times do |i|
13
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
14
+ end
15
+ TBLENCWWWCOMP_[' '] = '+'
16
+ TBLENCWWWCOMP_.freeze
17
+
18
+ 256.times do |i|
19
+ h, l = i>>4, i&15
20
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
21
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
22
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
23
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
24
+ end
25
+ TBLDECWWWCOMP_['+'] = ' '
26
+ TBLDECWWWCOMP_.freeze
27
+ end
28
+
29
+ # :startdoc:
@@ -0,0 +1,295 @@
1
+ module Puma::Rack
2
+ class Options
3
+ def parse!(args)
4
+ options = {}
5
+ opt_parser = OptionParser.new("", 24, ' ') do |opts|
6
+ opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
7
+
8
+ opts.separator ""
9
+ opts.separator "Ruby options:"
10
+
11
+ lineno = 1
12
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
13
+ eval line, TOPLEVEL_BINDING, "-e", lineno
14
+ lineno += 1
15
+ }
16
+
17
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
18
+ options[:builder] = line
19
+ }
20
+
21
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
22
+ options[:debug] = true
23
+ }
24
+ opts.on("-w", "--warn", "turn warnings on for your script") {
25
+ options[:warn] = true
26
+ }
27
+ opts.on("-q", "--quiet", "turn off logging") {
28
+ options[:quiet] = true
29
+ }
30
+
31
+ opts.on("-I", "--include PATH",
32
+ "specify $LOAD_PATH (may be used more than once)") { |path|
33
+ (options[:include] ||= []).concat(path.split(":"))
34
+ }
35
+
36
+ opts.on("-r", "--require LIBRARY",
37
+ "require the library, before executing your script") { |library|
38
+ options[:require] = library
39
+ }
40
+
41
+ opts.separator ""
42
+ opts.separator "Rack options:"
43
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
44
+ options[:server] = s
45
+ }
46
+
47
+ opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
48
+ options[:Host] = host
49
+ }
50
+
51
+ opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
52
+ options[:Port] = port
53
+ }
54
+
55
+ opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
56
+ name, value = name.split('=', 2)
57
+ value = true if value.nil?
58
+ options[name.to_sym] = value
59
+ }
60
+
61
+ opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
62
+ options[:environment] = e
63
+ }
64
+
65
+ opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
66
+ options[:daemonize] = d ? true : false
67
+ }
68
+
69
+ opts.on("-P", "--pid FILE", "file to store PID") { |f|
70
+ options[:pid] = ::File.expand_path(f)
71
+ }
72
+
73
+ opts.separator ""
74
+ opts.separator "Common options:"
75
+
76
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
77
+ puts opts
78
+ puts handler_opts(options)
79
+
80
+ exit
81
+ end
82
+
83
+ opts.on_tail("--version", "Show version") do
84
+ puts "Rack #{Rack.version} (Release: #{Rack.release})"
85
+ exit
86
+ end
87
+ end
88
+
89
+ begin
90
+ opt_parser.parse! args
91
+ rescue OptionParser::InvalidOption => e
92
+ warn e.message
93
+ abort opt_parser.to_s
94
+ end
95
+
96
+ options[:config] = args.last if args.last
97
+ options
98
+ end
99
+
100
+ def handler_opts(options)
101
+ begin
102
+ info = []
103
+ server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
104
+ if server && server.respond_to?(:valid_options)
105
+ info << ""
106
+ info << "Server-specific options for #{server.name}:"
107
+
108
+ has_options = false
109
+ server.valid_options.each do |name, description|
110
+ next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
111
+ info << " -O %-21s %s" % [name, description]
112
+ has_options = true
113
+ end
114
+ return "" if !has_options
115
+ end
116
+ info.join("\n")
117
+ rescue NameError
118
+ return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
119
+ end
120
+ end
121
+ end
122
+
123
+ # Rack::Builder implements a small DSL to iteratively construct Rack
124
+ # applications.
125
+ #
126
+ # Example:
127
+ #
128
+ # require 'rack/lobster'
129
+ # app = Rack::Builder.new do
130
+ # use Rack::CommonLogger
131
+ # use Rack::ShowExceptions
132
+ # map "/lobster" do
133
+ # use Rack::Lint
134
+ # run Rack::Lobster.new
135
+ # end
136
+ # end
137
+ #
138
+ # run app
139
+ #
140
+ # Or
141
+ #
142
+ # app = Rack::Builder.app do
143
+ # use Rack::CommonLogger
144
+ # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
145
+ # end
146
+ #
147
+ # run app
148
+ #
149
+ # +use+ adds middleware to the stack, +run+ dispatches to an application.
150
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
151
+
152
+ class Builder
153
+ def self.parse_file(config, opts = Options.new)
154
+ options = {}
155
+ if config =~ /\.ru$/
156
+ cfgfile = ::File.read(config)
157
+ if cfgfile[/^#\\(.*)/] && opts
158
+ options = opts.parse! $1.split(/\s+/)
159
+ end
160
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
161
+ app = new_from_string cfgfile, config
162
+ else
163
+ require config
164
+ app = Object.const_get(::File.basename(config, '.rb').capitalize)
165
+ end
166
+ return app, options
167
+ end
168
+
169
+ def self.new_from_string(builder_script, file="(rackup)")
170
+ eval "Puma::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
171
+ TOPLEVEL_BINDING, file, 0
172
+ end
173
+
174
+ def initialize(default_app = nil,&block)
175
+ @use, @map, @run, @warmup = [], nil, default_app, nil
176
+
177
+ # Conditionally load rack now, so that any rack middlewares,
178
+ # etc are available.
179
+ begin
180
+ require 'rack'
181
+ rescue LoadError
182
+ end
183
+
184
+ instance_eval(&block) if block_given?
185
+ end
186
+
187
+ def self.app(default_app = nil, &block)
188
+ self.new(default_app, &block).to_app
189
+ end
190
+
191
+ # Specifies middleware to use in a stack.
192
+ #
193
+ # class Middleware
194
+ # def initialize(app)
195
+ # @app = app
196
+ # end
197
+ #
198
+ # def call(env)
199
+ # env["rack.some_header"] = "setting an example"
200
+ # @app.call(env)
201
+ # end
202
+ # end
203
+ #
204
+ # use Middleware
205
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
206
+ #
207
+ # All requests through to this application will first be processed by the middleware class.
208
+ # The +call+ method in this example sets an additional environment key which then can be
209
+ # referenced in the application if required.
210
+ def use(middleware, *args, &block)
211
+ if @map
212
+ mapping, @map = @map, nil
213
+ @use << proc { |app| generate_map app, mapping }
214
+ end
215
+ @use << proc { |app| middleware.new(app, *args, &block) }
216
+ end
217
+
218
+ # Takes an argument that is an object that responds to #call and returns a Rack response.
219
+ # The simplest form of this is a lambda object:
220
+ #
221
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
222
+ #
223
+ # However this could also be a class:
224
+ #
225
+ # class Heartbeat
226
+ # def self.call(env)
227
+ # [200, { "Content-Type" => "text/plain" }, ["OK"]]
228
+ # end
229
+ # end
230
+ #
231
+ # run Heartbeat
232
+ def run(app)
233
+ @run = app
234
+ end
235
+
236
+ # Takes a lambda or block that is used to warm-up the application.
237
+ #
238
+ # warmup do |app|
239
+ # client = Rack::MockRequest.new(app)
240
+ # client.get('/')
241
+ # end
242
+ #
243
+ # use SomeMiddleware
244
+ # run MyApp
245
+ def warmup(prc=nil, &block)
246
+ @warmup = prc || block
247
+ end
248
+
249
+ # Creates a route within the application.
250
+ #
251
+ # Rack::Builder.app do
252
+ # map '/' do
253
+ # run Heartbeat
254
+ # end
255
+ # end
256
+ #
257
+ # The +use+ method can also be used here to specify middleware to run under a specific path:
258
+ #
259
+ # Rack::Builder.app do
260
+ # map '/' do
261
+ # use Middleware
262
+ # run Heartbeat
263
+ # end
264
+ # end
265
+ #
266
+ # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
267
+ #
268
+ def map(path, &block)
269
+ @map ||= {}
270
+ @map[path] = block
271
+ end
272
+
273
+ def to_app
274
+ app = @map ? generate_map(@run, @map) : @run
275
+ fail "missing run or map statement" unless app
276
+ app = @use.reverse.inject(app) { |a,e| e[a] }
277
+ @warmup.call(app) if @warmup
278
+ app
279
+ end
280
+
281
+ def call(env)
282
+ to_app.call(env)
283
+ end
284
+
285
+ private
286
+
287
+ def generate_map(default_app, mapping)
288
+ require 'puma/rack/urlmap'
289
+
290
+ mapped = default_app ? {'/' => default_app} : {}
291
+ mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
292
+ URLMap.new(mapped)
293
+ end
294
+ end
295
+ end