Class: Shotgun::Loader

Inherits:
Object
  • Object
show all
Includes:
Rack::Utils
Defined in:
lib/shotgun/loader.rb

Overview

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.

Defined Under Namespace

Classes: Body

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Loader) initialize(rackup_file, &block)



12
13
14
15
# File 'lib/shotgun/loader.rb', line 12

def initialize(rackup_file, &block)
  @rackup_file = rackup_file
  @config = block || Proc.new { }
end

Instance Attribute Details

- (Object) rackup_file (readonly)

Returns the value of attribute rackup_file



10
11
12
# File 'lib/shotgun/loader.rb', line 10

def rackup_file
  @rackup_file
end

Instance Method Details

- (Object) assemble_app



100
101
102
103
104
105
106
107
# File 'lib/shotgun/loader.rb', line 100

def assemble_app
  config = @config
  inner_app = self.inner_app
  Rack::Builder.new {
    instance_eval(&config)
    run inner_app
  }.to_app
end

- (Object) call(env)



17
18
19
# File 'lib/shotgun/loader.rb', line 17

def call(env)
  dup.call!(env)
end

- (Object) call!(env)



21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/shotgun/loader.rb', line 21

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

- (Object) camel_case(string)



126
127
128
# File 'lib/shotgun/loader.rb', line 126

def camel_case(string)
  string.split("_").map { |part| part.capitalize }.join
end

- (Object) format_error(error, backtrace)



73
74
75
76
77
78
# File 'lib/shotgun/loader.rb', line 73

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

- (Object) inner_app



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/shotgun/loader.rb', line 109

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

- (Object) proceed_as_child

Stuff that happens in the child process



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/shotgun/loader.rb', line 83

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

- (Object) proceed_as_parent

Stuff that happens in the parent process



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/shotgun/loader.rb', line 38

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

- (Object) spec_body(body)



130
131
132
133
134
135
136
137
138
# File 'lib/shotgun/loader.rb', line 130

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