123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- require 'rack/utils'
- require 'thread'
- module Shotgun
- # Rack app that forks, loads the rackup config in the child process,
- # processes a single request, and exits. The response is communicated over
- # a unidirectional pipe.
- class Loader
- include Rack::Utils
- attr_reader :rackup_file
- def initialize(rackup_file, &block)
- @rackup_file = rackup_file
- @config = block || Proc.new { }
- end
- def call(env)
- dup.call!(env)
- end
- def call!(env)
- @env = env
- @reader, @writer = IO.pipe
- Shotgun.before_fork!
- if @child = fork
- proceed_as_parent
- else
- Shotgun.after_fork!
- proceed_as_child
- end
- end
- ##
- # Stuff that happens in the parent process
- def proceed_as_parent
- @writer.close
- rand
- result, status, headers = Marshal.load(@reader)
- body = Body.new(@child, @reader)
- case result
- when :ok
- [status, headers, body]
- when :error
- error, backtrace = status, headers
- body.close
- [
- 500,
- {'Content-Type'=>'text/html;charset=utf-8'},
- [format_error(error, backtrace)]
- ]
- else
- fail "unexpected response: #{result.inspect}"
- end
- end
- class Body < Struct.new(:pid, :fd)
- def each
- while chunk = fd.read(1024)
- yield chunk
- end
- end
- def close
- fd.close
- ensure
- Process.wait(pid)
- end
- end
- def format_error(error, backtrace)
- "<h1>Boot Error</h1>" +
- "<p>Something went wrong while loading <tt>#{escape_html(rackup_file)}</tt></p>" +
- "<h3>#{escape_html(error)}</h3>" +
- "<pre>#{escape_html(backtrace.join("\n"))}</pre>"
- end
- ##
- # Stuff that happens in the child process
- def proceed_as_child
- boom = false
- @reader.close
- status, headers, body = assemble_app.call(@env)
- Marshal.dump([:ok, status, headers.to_hash], @writer)
- spec_body(body).each { |chunk| @writer.write(chunk) }
- rescue Object => boom
- Marshal.dump([
- :error,
- "#{boom.class.name}: #{boom.to_s}",
- boom.backtrace
- ], @writer)
- ensure
- @writer.close
- exit! boom ? 1 : 0
- end
- def assemble_app
- config = @config
- inner_app = self.inner_app
- Rack::Builder.new {
- instance_eval(&config)
- run inner_app
- }.to_app
- end
- def inner_app
- if rackup_file =~ /\.ru$/
- config = File.read(rackup_file)
- eval "Rack::Builder.new {( #{config}\n )}.to_app", nil, rackup_file
- else
- require File.expand_path(rackup_file)
- if defined? Sinatra::Application
- Sinatra::Application.set :reload, false
- Sinatra::Application.set :logging, false
- Sinatra::Application.set :raise_errors, true
- Sinatra::Application
- else
- Object.const_get(camel_case(File.basename(rackup_file, '.rb')))
- end
- end
- end
- def camel_case(string)
- string.split("_").map { |part| part.capitalize }.join
- end
- def spec_body(body)
- if body.respond_to? :to_str
- [body]
- elsif body.respond_to?(:each)
- body
- else
- fail "body must respond to #each"
- end
- end
- end
- end
|