puma 2.0.0.b4-java → 2.0.0.b7-java

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.

@@ -11,6 +11,7 @@ require 'puma/client'
11
11
  require 'puma/binder'
12
12
  require 'puma/delegation'
13
13
  require 'puma/accept_nonblock'
14
+ require 'puma/util'
14
15
 
15
16
  require 'puma/puma_http11'
16
17
 
@@ -49,7 +50,7 @@ module Puma
49
50
  @app = app
50
51
  @events = events
51
52
 
52
- @check, @notify = IO.pipe
53
+ @check, @notify = Puma::Util.pipe
53
54
 
54
55
  @status = :stop
55
56
 
@@ -61,9 +62,10 @@ module Puma
61
62
  @thread_pool = nil
62
63
 
63
64
  @persistent_timeout = PERSISTENT_TIMEOUT
64
- @persistent_check, @persistent_wakeup = IO.pipe
65
65
 
66
66
  @binder = Binder.new(events)
67
+ @own_binder = true
68
+
67
69
  @first_data_timeout = FIRST_DATA_TIMEOUT
68
70
 
69
71
  ENV['RACK_ENV'] ||= "development"
@@ -75,6 +77,11 @@ module Puma
75
77
  forward :add_ssl_listener, :@binder
76
78
  forward :add_unix_listener, :@binder
77
79
 
80
+ def inherit_binder(bind)
81
+ @binder = bind
82
+ @own_binder = false
83
+ end
84
+
78
85
  # On Linux, use TCP_CORK to better control how the TCP stack
79
86
  # packetizes our stream. This improves both latency and throughput.
80
87
  #
@@ -190,9 +197,14 @@ module Puma
190
197
  @reactor.clear! if @status == :restart
191
198
 
192
199
  @reactor.shutdown
200
+ rescue Exception => e
201
+ STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
202
+ STDERR.puts e.backtrace
193
203
  ensure
194
- unless @status == :restart
195
- @check.close
204
+ @check.close
205
+ @notify.close
206
+
207
+ if @status != :restart and @own_binder
196
208
  @binder.close
197
209
  end
198
210
  end
@@ -285,11 +297,11 @@ module Puma
285
297
  env[SERVER_PORT] = host[colon+1, host.bytesize]
286
298
  else
287
299
  env[SERVER_NAME] = host
288
- env[SERVER_PORT] = PORT_80
300
+ env[SERVER_PORT] = default_server_port(env)
289
301
  end
290
302
  else
291
303
  env[SERVER_NAME] = LOCALHOST
292
- env[SERVER_PORT] = PORT_80
304
+ env[SERVER_PORT] = default_server_port(env)
293
305
  end
294
306
 
295
307
  unless env[REQUEST_PATH]
@@ -313,6 +325,10 @@ module Puma
313
325
  env[REMOTE_ADDR] = client.peeraddr.last
314
326
  end
315
327
 
328
+ def default_server_port(env)
329
+ env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
330
+ end
331
+
316
332
  # Given the request +env+ from +client+ and a partial request body
317
333
  # in +body+, finish reading the body if there is one and invoke
318
334
  # the rack app. Then construct the response and write it back to
@@ -330,6 +346,9 @@ module Puma
330
346
 
331
347
  env[PUMA_SOCKET] = client
332
348
 
349
+ env[HIJACK_P] = true
350
+ env[HIJACK] = req
351
+
333
352
  body = req.body
334
353
 
335
354
  env[RACK_INPUT] = body
@@ -343,6 +362,9 @@ module Puma
343
362
  begin
344
363
  begin
345
364
  status, headers, res_body = @app.call(env)
365
+
366
+ return :async if req.hijacked
367
+
346
368
  status = status.to_i
347
369
 
348
370
  if status == -1
@@ -404,6 +426,8 @@ module Puma
404
426
  end
405
427
  end
406
428
 
429
+ response_hijack = nil
430
+
407
431
  headers.each do |k, vs|
408
432
  case k
409
433
  when CONTENT_LENGTH2
@@ -414,6 +438,9 @@ module Puma
414
438
  content_length = nil
415
439
  when CONTENT_TYPE
416
440
  next if no_body
441
+ when HIJACK
442
+ response_hijack = vs
443
+ next
417
444
  end
418
445
 
419
446
  vs.split(NEWLINE).each do |v|
@@ -433,18 +460,25 @@ module Puma
433
460
  lines << CONNECTION_CLOSE
434
461
  end
435
462
 
436
- if content_length
437
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
438
- chunked = false
439
- elsif allow_chunked
440
- lines << TRANSFER_ENCODING_CHUNKED
441
- chunked = true
463
+ unless response_hijack
464
+ if content_length
465
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
466
+ chunked = false
467
+ elsif allow_chunked
468
+ lines << TRANSFER_ENCODING_CHUNKED
469
+ chunked = true
470
+ end
442
471
  end
443
472
 
444
473
  lines << line_ending
445
474
 
446
475
  fast_write client, lines.to_s
447
476
 
477
+ if response_hijack
478
+ response_hijack.call client
479
+ return :async
480
+ end
481
+
448
482
  res_body.each do |part|
449
483
  if chunked
450
484
  client.syswrite part.bytesize.to_s(16)
@@ -545,38 +579,33 @@ module Puma
545
579
  # off the request queue before finally exiting.
546
580
  #
547
581
  def stop(sync=false)
548
- @persistent_wakeup.close
549
582
  @notify << STOP_COMMAND
550
583
 
551
584
  @thread.join if @thread && sync
552
585
  end
553
586
 
554
587
  def halt(sync=false)
555
- @persistent_wakeup.close
556
588
  @notify << HALT_COMMAND
557
589
 
558
590
  @thread.join if @thread && sync
559
591
  end
560
592
 
561
593
  def begin_restart
562
- @persistent_wakeup.close
563
594
  @notify << RESTART_COMMAND
564
595
  end
565
596
 
566
597
  def fast_write(io, str)
567
- n = io.syswrite str
568
-
569
- # Fast path.
570
- return if n == str.bytesize
571
-
572
- pos = n
573
- left = str.bytesize - n
574
-
575
- until left == 0
576
- n = io.syswrite str.byteslice(pos..-1)
598
+ n = 0
599
+ while true
600
+ begin
601
+ n = io.syswrite str
602
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
603
+ IO.select(nil, [io], nil, 1)
604
+ retry
605
+ end
577
606
 
578
- pos += n
579
- left -= n
607
+ return if n == str.bytesize
608
+ str = str.byteslice(n..-1)
580
609
  end
581
610
  end
582
611
  private :fast_write
@@ -0,0 +1,9 @@
1
+ module Puma
2
+ module Util
3
+ module_function
4
+
5
+ def pipe
6
+ IO.pipe
7
+ end
8
+ end
9
+ end
@@ -2,23 +2,23 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "puma"
5
- s.version = "2.0.0.b4"
5
+ s.version = "2.0.0.b7"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Evan Phoenix"]
9
- s.date = "2012-12-13"
9
+ s.date = "2013-03-19"
10
10
  s.description = "Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications. It can be used with any application that supports Rack, and is considered the replacement for Webrick and Mongrel. It was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI. Puma is intended for use in both development and production environments.\n\nUnder the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool (which you can control). This allows Puma to provide real concurrency for your web application!\n\nWith Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning you won't have to spawn multiple processes to increase throughput. You can expect to see a similar benefit from JRuby.\n\nOn MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can be run at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently (EventMachine-based servers such as Thin turn off this ability, requiring you to use special libraries). Your mileage may vary. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org)."
11
11
  s.email = ["evan@phx.io"]
12
12
  s.executables = ["puma", "pumactl"]
13
13
  s.extensions = ["ext/puma_http11/extconf.rb"]
14
14
  s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
15
- s.files = ["COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "docs/config.md", "docs/nginx.md", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/io_buffer.c", "ext/puma_http11/mini_ssl.c", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/org/jruby/puma/MiniSSL.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/accept_nonblock.rb", "lib/puma/app/status.rb", "lib/puma/binder.rb", "lib/puma/capistrano.rb", "lib/puma/cli.rb", "lib/puma/client.rb", "lib/puma/compat.rb", "lib/puma/configuration.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/daemon_ext.rb", "lib/puma/delegation.rb", "lib/puma/detect.rb", "lib/puma/events.rb", "lib/puma/io_buffer.rb", "lib/puma/java_io_buffer.rb", "lib/puma/jruby_restart.rb", "lib/puma/minissl.rb", "lib/puma/null_io.rb", "lib/puma/rack_patch.rb", "lib/puma/reactor.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "tools/jungle/README.md", "tools/jungle/puma", "tools/jungle/run-puma", "test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_iobuffer.rb", "test/test_minissl.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
15
+ s.files = ["COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "docs/config.md", "docs/nginx.md", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/io_buffer.c", "ext/puma_http11/mini_ssl.c", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/org/jruby/puma/MiniSSL.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/accept_nonblock.rb", "lib/puma/app/status.rb", "lib/puma/binder.rb", "lib/puma/capistrano.rb", "lib/puma/cli.rb", "lib/puma/client.rb", "lib/puma/compat.rb", "lib/puma/configuration.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/daemon_ext.rb", "lib/puma/delegation.rb", "lib/puma/detect.rb", "lib/puma/events.rb", "lib/puma/io_buffer.rb", "lib/puma/java_io_buffer.rb", "lib/puma/jruby_restart.rb", "lib/puma/minissl.rb", "lib/puma/null_io.rb", "lib/puma/rack_default.rb", "lib/puma/rack_patch.rb", "lib/puma/reactor.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/puma/util.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "tools/jungle/init.d/README.md", "tools/jungle/init.d/puma", "tools/jungle/init.d/run-puma", "tools/jungle/upstart/README.md", "tools/jungle/upstart/puma-manager.conf", "tools/jungle/upstart/puma.conf", "test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_iobuffer.rb", "test/test_minissl.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
16
16
  s.homepage = "http://puma.io"
17
17
  s.rdoc_options = ["--main", "README.md"]
18
18
  s.require_paths = ["lib"]
19
19
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
20
20
  s.rubyforge_project = "puma"
21
- s.rubygems_version = "1.8.24"
21
+ s.rubygems_version = "1.8.25"
22
22
  s.summary = "Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications"
23
23
  s.test_files = ["test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_iobuffer.rb", "test/test_minissl.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
24
24
 
@@ -26,20 +26,20 @@ Gem::Specification.new do |s|
26
26
  s.specification_version = 3
27
27
 
28
28
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
29
- s.add_runtime_dependency(%q<rack>, ["~> 1.2"])
29
+ s.add_runtime_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
30
30
  s.add_development_dependency(%q<rdoc>, ["~> 3.10"])
31
31
  s.add_development_dependency(%q<rake-compiler>, ["~> 0.8.0"])
32
- s.add_development_dependency(%q<hoe>, ["~> 3.0"])
32
+ s.add_development_dependency(%q<hoe>, ["~> 3.5"])
33
33
  else
34
- s.add_dependency(%q<rack>, ["~> 1.2"])
34
+ s.add_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
35
35
  s.add_dependency(%q<rdoc>, ["~> 3.10"])
36
36
  s.add_dependency(%q<rake-compiler>, ["~> 0.8.0"])
37
- s.add_dependency(%q<hoe>, ["~> 3.0"])
37
+ s.add_dependency(%q<hoe>, ["~> 3.5"])
38
38
  end
39
39
  else
40
- s.add_dependency(%q<rack>, ["~> 1.2"])
40
+ s.add_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
41
41
  s.add_dependency(%q<rdoc>, ["~> 3.10"])
42
42
  s.add_dependency(%q<rake-compiler>, ["~> 0.8.0"])
43
- s.add_dependency(%q<hoe>, ["~> 3.0"])
43
+ s.add_dependency(%q<hoe>, ["~> 3.5"])
44
44
  end
45
45
  end
@@ -11,6 +11,6 @@ class TestConfigFile < Test::Unit::TestCase
11
11
 
12
12
  app = conf.app
13
13
 
14
- assert_equal [200, {}, ["embedded app"]], app.call(nil)
14
+ assert_equal [200, {}, ["embedded app"]], app.call({})
15
15
  end
16
16
  end
@@ -1,5 +1,7 @@
1
1
  require 'test/unit'
2
2
 
3
+ unless defined? JRUBY_VERSION
4
+
3
5
  require 'puma'
4
6
  require 'puma/minissl'
5
7
 
@@ -10,13 +12,14 @@ class TestMiniSSL < Test::Unit::TestCase
10
12
 
11
13
  exception = assert_raise(ArgumentError) { ctx.key = "/no/such/key" }
12
14
  assert_equal("No such key file '/no/such/key'", exception.message)
13
- end unless defined? JRUBY_VERSION
15
+ end
14
16
 
15
17
  def test_raises_with_invalid_cert_file
16
18
  ctx = Puma::MiniSSL::Context.new
17
19
 
18
20
  exception = assert_raise(ArgumentError) { ctx.cert = "/no/such/cert" }
19
21
  assert_equal("No such cert file '/no/such/cert'", exception.message)
20
- end unless defined? JRUBY_VERSION
22
+ end
23
+ end
21
24
 
22
25
  end
@@ -125,4 +125,41 @@ class TestPumaServer < Test::Unit::TestCase
125
125
 
126
126
  assert_equal giant.bytesize, out.bytesize
127
127
  end
128
+
129
+ def test_respect_x_forwarded_proto
130
+ @server.app = proc do |env|
131
+ [200, {}, [env['SERVER_PORT']]]
132
+ end
133
+
134
+ @server.add_tcp_listener @host, @port
135
+ @server.run
136
+
137
+ req = Net::HTTP::Get.new("/")
138
+ req['HOST'] = "example.com"
139
+ req['X_FORWARDED_PROTO'] = "https"
140
+
141
+ res = Net::HTTP.start @host, @port do |http|
142
+ http.request(req)
143
+ end
144
+
145
+ assert_equal "443", res.body
146
+ end
147
+
148
+ def test_default_server_port
149
+ @server.app = proc do |env|
150
+ [200, {}, [env['SERVER_PORT']]]
151
+ end
152
+
153
+ @server.add_tcp_listener @host, @port
154
+ @server.run
155
+
156
+ req = Net::HTTP::Get.new("/")
157
+ req['HOST'] = "example.com"
158
+
159
+ res = Net::HTTP.start @host, @port do |http|
160
+ http.request(req)
161
+ end
162
+
163
+ assert_equal "80", res.body
164
+ end
128
165
  end
File without changes
@@ -0,0 +1,61 @@
1
+ # Puma as a service using Upstart
2
+
3
+ Manage multiple Puma servers as services on the same box using Ubuntu upstart.
4
+
5
+ ## Installation
6
+
7
+ # Copy the scripts to services directory
8
+ sudo cp puma.conf puma-manager.conf /etc/init
9
+
10
+ # Create an empty configuration file
11
+ sudo touch /etc/puma.conf
12
+
13
+ ## Managing the jungle
14
+
15
+ Puma apps are referenced in /etc/puma.conf by default. Add each app's path as a new line, e.g.:
16
+
17
+ ```
18
+ /home/apps/my-cool-ruby-app
19
+ /home/apps/another-app/current
20
+ ```
21
+
22
+ Start the jungle running:
23
+
24
+ `sudo start puma-manager`
25
+
26
+ This script will run at boot time.
27
+
28
+ Start a single puma like this:
29
+
30
+ `sudo start puma app=/path/to/app`
31
+
32
+ ## Logs
33
+
34
+ Everything is logged by upstart, defaulting to `/var/log/upstart`.
35
+
36
+ Each puma instance is named after its directory, so for an app called `/home/apps/my-app` the log file would be `/var/log/upstart/puma-_home_apps_my-app.log`.
37
+
38
+ ## Conventions
39
+
40
+ * The script expects:
41
+ * a config file to exist under `config/puma.rb` in your app. E.g.: `/home/apps/my-app/config/puma.rb`.
42
+ * a temporary folder to put the PID, socket and state files to exist called `tmp/puma`. E.g.: `/home/apps/my-app/tmp/puma`. Puma will take care of the files for you.
43
+
44
+ You can always change those defaults by editing the scripts.
45
+
46
+ ## Here's what a minimal app's config file should have
47
+
48
+ ```
49
+ pidfile "/path/to/app/tmp/puma/pid"
50
+ state_path "/path/to/app/tmp/puma/state"
51
+ activate_control_app
52
+ ```
53
+
54
+ ## Before starting...
55
+
56
+ You need to customise `puma.conf` to:
57
+
58
+ * Set the right user your app should be running on unless you want root to execute it!
59
+ * Look for `setuid apps` and `setgid apps`, uncomment those lines and replace `apps` to whatever your deployment user is.
60
+ * Replace `apps` on the paths (or set the right paths to your user's home) everywhere else.
61
+ * Uncomment the source lines for `rbenv` or `rvm` support unless you use a system wide installation of Ruby.
@@ -0,0 +1,31 @@
1
+ # /etc/init/puma-manager.conf - manage a set of Pumas
2
+
3
+ # This example config should work with Ubuntu 12.04+. It
4
+ # allows you to manage multiple Puma instances with
5
+ # Upstart, Ubuntu's native service management tool.
6
+ #
7
+ # See puma.conf for how to manage a single Puma instance.
8
+ #
9
+ # Use "stop workers" to stop all Puma instances.
10
+ # Use "start workers" to start all instances.
11
+ # Use "restart workers" to restart all instances.
12
+ # Crazy, right?
13
+ #
14
+
15
+ description "Manages the set of puma processes"
16
+
17
+ # This starts upon bootup and stops on shutdown
18
+ start on runlevel [2345]
19
+ stop on runlevel [06]
20
+
21
+ # Set this to the number of Puma processes you want
22
+ # to run on this machine
23
+ env PUMA_CONF=/etc/puma.conf
24
+
25
+ pre-start script
26
+ for i in `cat $PUMA_CONF`; do
27
+ app=`echo $i | cut -d , -f 1`
28
+ logger -t "puma-manager" "Starting $app"
29
+ start puma app=$app
30
+ done
31
+ end script
@@ -0,0 +1,52 @@
1
+ # /etc/init/puma.conf - Puma config
2
+
3
+ # This example config should work with Ubuntu 12.04+. It
4
+ # allows you to manage multiple Puma instances with
5
+ # Upstart, Ubuntu's native service management tool.
6
+ #
7
+ # See workers.conf for how to manage all Puma instances at once.
8
+ #
9
+ # Save this config as /etc/init/puma.conf then mange puma with:
10
+ # sudo start puma index=0
11
+ # sudo stop puma index=0
12
+ # sudo status puma index=0
13
+ #
14
+ # or use the service command:
15
+ # sudo service puma {start,stop,restart,status}
16
+ #
17
+
18
+ description "Puma Background Worker"
19
+
20
+ # no "start on", we don't want to automatically start
21
+ stop on (stopping puma-manager or runlevel [06])
22
+
23
+ # change apps to match your deployment user if you want to use this as a less privileged user (recommended!)
24
+ # setuid apps
25
+ # setgid apps
26
+
27
+ respawn
28
+ respawn limit 3 30
29
+
30
+ instance ${app}
31
+
32
+ script
33
+ # this script runs in /bin/sh by default
34
+ # respawn as bash so we can source in rbenv/rvm
35
+ exec /bin/bash <<EOT
36
+ export HOME=/home/apps
37
+
38
+ # Pick your poison :) Or none if you're using a system wide installed Ruby.
39
+ # rbenv
40
+ # source /home/apps/.bash_profile
41
+ # OR
42
+ # source /home/apps/.profile
43
+ #
44
+ # rvm
45
+ # source /home/apps/.rvm/scripts/rvm
46
+
47
+ logger -t puma "Starting server: $app"
48
+
49
+ cd $app
50
+ exec bundle exec puma -C config/puma.rb
51
+ EOT
52
+ end script