shotgun 0.6 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +26 -33
- data/bin/shotgun +52 -48
- data/lib/shotgun.rb +7 -90
- data/lib/shotgun/favicon.rb +16 -0
- data/lib/shotgun/loader.rb +133 -0
- data/lib/shotgun/static.rb +20 -0
- data/man/index.txt +6 -0
- data/man/shotgun.1.ronn +134 -0
- data/shotgun.gemspec +21 -15
- data/test/big.ru +15 -0
- data/test/boom.ru +4 -0
- data/test/slow.ru +16 -0
- data/test/test-sinatra.ru +14 -0
- data/test/test_shotgun_loader.rb +35 -0
- data/test/test_shotgun_static.rb +26 -0
- metadata +47 -20
- data/test/shotgun_test.rb +0 -22
data/Rakefile
CHANGED
@@ -2,42 +2,35 @@ require 'rake/clean'
|
|
2
2
|
require 'rake/testtask'
|
3
3
|
|
4
4
|
task :default => [:test]
|
5
|
-
task :spec => :test
|
6
5
|
|
7
6
|
Rake::TestTask.new(:test) do |t|
|
8
|
-
t.test_files = FileList['test
|
7
|
+
t.test_files = FileList['test/test_shotgun_*.rb']
|
9
8
|
t.ruby_opts = ['-rubygems'] if defined? Gem
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
git archive \
|
38
|
-
--prefix=shotgun-#{$spec.version}/ \
|
39
|
-
--format=tar \
|
40
|
-
HEAD | gzip > #{f.name}
|
41
|
-
SH
|
42
|
-
end
|
11
|
+
desc "build manual"
|
12
|
+
task :man do
|
13
|
+
ENV['RONN_ORGANIZATION'] = "Shotgun #{SPEC.version}"
|
14
|
+
sh "ronn -5r -stoc man/*.ronn"
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
SPEC = eval(File.read('shotgun.gemspec'))
|
19
|
+
PACK = "#{SPEC.name}-#{SPEC.version}"
|
20
|
+
|
21
|
+
desc 'build packages'
|
22
|
+
task :package => %W[pkg/#{PACK}.gem pkg/#{PACK}.tar.gz]
|
23
|
+
|
24
|
+
directory 'pkg/'
|
25
|
+
|
26
|
+
file "pkg/#{PACK}.gem" => %w[pkg/ shotgun.gemspec] + SPEC.files do |f|
|
27
|
+
sh "gem build shotgun.gemspec"
|
28
|
+
mv File.basename(f.name), f.name
|
29
|
+
end
|
30
|
+
|
31
|
+
file "pkg/#{PACK}.tar.gz" => %w[pkg/] + SPEC.files do |f|
|
32
|
+
sh <<-SH
|
33
|
+
git archive --prefix=shotgun-#{SPEC.version}/ --format=tar HEAD |
|
34
|
+
gzip > '#{f.name}'
|
35
|
+
SH
|
43
36
|
end
|
data/bin/shotgun
CHANGED
@@ -2,16 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
|
5
|
-
begin
|
6
|
-
require 'thin'
|
7
|
-
server = 'thin'
|
8
|
-
rescue LoadError
|
9
|
-
server = nil
|
10
|
-
end
|
11
|
-
|
12
5
|
env = ENV['RACK_ENV'] || 'development'
|
13
6
|
browse = false
|
14
|
-
|
7
|
+
public_dir = 'public' if File.directory?('public')
|
8
|
+
options = {:Port => 9393, :Host => '127.0.0.1', :AccessLog => [], :Path => '/'}
|
15
9
|
|
16
10
|
opts = OptionParser.new("", 24, ' ') { |opts|
|
17
11
|
opts.banner = "Usage: shotgun [ruby options] [rack options] [rackup config]"
|
@@ -48,7 +42,7 @@ opts = OptionParser.new("", 24, ' ') { |opts|
|
|
48
42
|
server = s
|
49
43
|
}
|
50
44
|
|
51
|
-
opts.on("-o", "--host HOST", "listen on HOST (default:
|
45
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: 127.0.0.1)") { |host|
|
52
46
|
options[:Host] = host
|
53
47
|
}
|
54
48
|
|
@@ -63,12 +57,16 @@ opts = OptionParser.new("", 24, ' ') { |opts|
|
|
63
57
|
opts.separator ""
|
64
58
|
opts.separator "Shotgun options:"
|
65
59
|
|
60
|
+
opts.on("-O", "--browse", "open browser immediately after starting") { |browse|
|
61
|
+
browse = true
|
62
|
+
}
|
63
|
+
|
66
64
|
opts.on("-u", "--url URL", "specify url path (default: /)") { |url|
|
67
65
|
options[:Path] = url
|
68
66
|
}
|
69
67
|
|
70
|
-
opts.on("-
|
71
|
-
|
68
|
+
opts.on("-P", "--public PATH", "serve static files under PATH") { |path|
|
69
|
+
public_dir = path
|
72
70
|
}
|
73
71
|
|
74
72
|
opts.on_tail("-h", "--help", "show this message") do
|
@@ -86,12 +84,22 @@ opts = OptionParser.new("", 24, ' ') { |opts|
|
|
86
84
|
}
|
87
85
|
|
88
86
|
config = ARGV[0] || "config.ru"
|
89
|
-
abort "configuration #{config} not found"
|
87
|
+
abort "configuration #{config} not found" unless File.exist? config
|
90
88
|
|
91
|
-
|
89
|
+
# extract additional arguments from first #\ line in config file.
|
90
|
+
if File.read(config)[/^#\\(.*)/]
|
92
91
|
opts.parse! $1.split(/\s+/)
|
93
92
|
end
|
94
93
|
|
94
|
+
# use the BROWSER environment variable or fall back to a more or less standard
|
95
|
+
# set of commands
|
96
|
+
ENV['BROWSER'] ||=
|
97
|
+
%w[open xdg-open x-www-browser firefox opera mozilla netscape].find do |comm|
|
98
|
+
next if comm == 'open' && `uname` !~ /Darwin/
|
99
|
+
ENV['PATH'].split(':').any? { |dir| File.executable?("#{dir}/#{comm}") }
|
100
|
+
end
|
101
|
+
ENV['RACK_ENV'] = env
|
102
|
+
|
95
103
|
require 'rack'
|
96
104
|
require 'rack/utils'
|
97
105
|
require 'rack/request'
|
@@ -100,53 +108,49 @@ require 'rack/lint'
|
|
100
108
|
require 'rack/commonlogger'
|
101
109
|
require 'rack/showexceptions'
|
102
110
|
|
103
|
-
|
104
|
-
begin
|
105
|
-
server = Rack::Handler::Mongrel
|
106
|
-
rescue LoadError => e
|
107
|
-
server = Rack::Handler::WEBrick
|
108
|
-
end
|
109
|
-
end
|
111
|
+
require 'shotgun'
|
110
112
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
server = Rack::Handler.get(server) || Rack::Handler.default
|
114
|
+
app =
|
115
|
+
Rack::Builder.new do
|
116
|
+
# these middleware run in the master process.
|
117
|
+
use Shotgun::Static, public_dir if public_dir
|
118
|
+
use Shotgun::SkipFavicon
|
119
|
+
|
120
|
+
# loader forks the child and runs the embedded config followed by the
|
121
|
+
# application config.
|
122
|
+
run Shotgun::Loader.new(config) {
|
123
|
+
case env
|
124
|
+
when 'development'
|
116
125
|
use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
|
117
126
|
use Rack::ShowExceptions
|
118
127
|
use Rack::Lint
|
119
|
-
|
120
|
-
}.to_app
|
121
|
-
when 'deployment', 'production'
|
122
|
-
Rack::Builder.new {
|
128
|
+
when 'deployment', 'production'
|
123
129
|
use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
|
124
|
-
|
125
|
-
|
126
|
-
else
|
127
|
-
inner_app
|
128
|
-
end
|
130
|
+
end
|
131
|
+
}
|
129
132
|
end
|
130
133
|
|
131
|
-
|
132
|
-
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
Shotgun.enable_copy_on_write
|
135
|
+
|
136
|
+
# trap exit signals
|
137
|
+
downward = false
|
138
|
+
['INT', 'TERM', 'QUIT'].each do |signal|
|
139
|
+
trap(signal) do
|
140
|
+
exit! if downward
|
141
|
+
downward = true
|
142
|
+
server.shutdown if server.respond_to?(:shutdown)
|
143
|
+
Process.wait rescue nil
|
144
|
+
exit!
|
139
145
|
end
|
146
|
+
end
|
140
147
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
base_url = "http://#{options[:Host]}:#{options[:Port]}"
|
145
|
-
puts "== Shotgun starting #{server.to_s} at #{base_url}"
|
148
|
+
base_url = "http://#{options[:Host]}:#{options[:Port]}#{options[:Path]}"
|
149
|
+
puts "== Shotgun/#{server.to_s.sub(/Rack::Handler::/, '')} on #{base_url}"
|
146
150
|
server.run app, options do |inst|
|
147
151
|
if browse
|
148
152
|
if ENV['BROWSER']
|
149
|
-
system "#{ENV['BROWSER']} '#{base_url}
|
153
|
+
system "#{ENV['BROWSER']} '#{base_url}'"
|
150
154
|
else
|
151
155
|
abort "BROWSER environment variable not set and no browser detected"
|
152
156
|
end
|
data/lib/shotgun.rb
CHANGED
@@ -1,98 +1,15 @@
|
|
1
1
|
require 'rack'
|
2
|
-
require 'rack/utils'
|
3
|
-
require 'thread'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Shotgun
|
4
|
+
autoload :Loader, 'shotgun/loader'
|
5
|
+
autoload :SkipFavicon, 'shotgun/favicon'
|
6
|
+
autoload :Static, 'shotgun/static'
|
8
7
|
|
9
|
-
def
|
10
|
-
|
11
|
-
@wrapper = wrapper || lambda { |inner_app| inner_app }
|
12
|
-
enable_copy_on_write
|
8
|
+
def self.new(rackup_file, &block)
|
9
|
+
Loader.new(rackup_file, &block)
|
13
10
|
end
|
14
11
|
|
15
|
-
def
|
16
|
-
dup.call!(env)
|
17
|
-
end
|
18
|
-
|
19
|
-
def call!(env)
|
20
|
-
@env = env
|
21
|
-
@reader, @writer = IO.pipe
|
22
|
-
|
23
|
-
if @child = fork
|
24
|
-
proceed_as_parent
|
25
|
-
else
|
26
|
-
proceed_as_child
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# ==== Stuff that happens in the parent process
|
31
|
-
|
32
|
-
def proceed_as_parent
|
33
|
-
rand # Reseeds
|
34
|
-
@writer.close
|
35
|
-
result = Marshal.load(@reader)
|
36
|
-
@reader.close
|
37
|
-
Process.wait(@child)
|
38
|
-
if result.length == 3
|
39
|
-
result
|
40
|
-
else
|
41
|
-
[500, {'Content-Type'=>'text/html;charset=utf-8'}, [format_error(result)]]
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def format_error(result)
|
46
|
-
message, backtrace = result
|
47
|
-
"<h1>FAIL</h1><h3>#{escape_html(message)}</h3>" +
|
48
|
-
"<pre>#{escape_html(backtrace.join("\n"))}</pre>"
|
49
|
-
end
|
50
|
-
|
51
|
-
# ==== Stuff that happens in the forked child process.
|
52
|
-
|
53
|
-
def proceed_as_child
|
54
|
-
@reader.close
|
55
|
-
app = assemble_app
|
56
|
-
status, headers, body = app.call(@env)
|
57
|
-
Marshal.dump([status, headers.to_hash, slurp(body)], @writer)
|
58
|
-
@writer.close
|
59
|
-
rescue Object => boom
|
60
|
-
Marshal.dump(["#{boom.class.name}: #{boom.to_s}", boom.backtrace], @writer)
|
61
|
-
ensure
|
62
|
-
exit! 0
|
63
|
-
end
|
64
|
-
|
65
|
-
def assemble_app
|
66
|
-
@wrapper.call(inner_app)
|
67
|
-
end
|
68
|
-
|
69
|
-
def inner_app
|
70
|
-
if rackup_file =~ /\.ru$/
|
71
|
-
config = File.read(rackup_file)
|
72
|
-
eval "Rack::Builder.new {( #{config}\n )}.to_app", nil, rackup_file
|
73
|
-
else
|
74
|
-
require rackup_file
|
75
|
-
if defined? Sinatra::Application
|
76
|
-
Sinatra::Application.set :reload, false
|
77
|
-
Sinatra::Application.set :logging, false
|
78
|
-
Sinatra::Application.set :raise_errors, true
|
79
|
-
Sinatra::Application
|
80
|
-
else
|
81
|
-
Object.const_get(File.basename(rackup_file, '.rb').capitalize)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def slurp(body)
|
87
|
-
return body if body.respond_to? :to_ary
|
88
|
-
return [body] if body.respond_to? :to_str
|
89
|
-
|
90
|
-
buf = []
|
91
|
-
body.each { |part| buf << part }
|
92
|
-
buf
|
93
|
-
end
|
94
|
-
|
95
|
-
def enable_copy_on_write
|
12
|
+
def self.enable_copy_on_write
|
96
13
|
GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
|
97
14
|
end
|
98
15
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Shotgun
|
2
|
+
# Responds to requests for /favicon.ico with a content free 404 and caching
|
3
|
+
# headers.
|
4
|
+
class SkipFavicon < Struct.new(:app)
|
5
|
+
def call(env)
|
6
|
+
if env['PATH_INFO'] == '/favicon.ico'
|
7
|
+
[404, {
|
8
|
+
'Content-Type' => 'image/png',
|
9
|
+
'Cache-Control' => 'public, max-age=100000000000'
|
10
|
+
}, []]
|
11
|
+
else
|
12
|
+
app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Shotgun
|
5
|
+
# Rack app that forks, loads the rackup config in the child process,
|
6
|
+
# processes a single request, and exits. The response is communicated over
|
7
|
+
# a unidirectional pipe.
|
8
|
+
class Loader
|
9
|
+
include Rack::Utils
|
10
|
+
attr_reader :rackup_file
|
11
|
+
|
12
|
+
def initialize(rackup_file, &block)
|
13
|
+
@rackup_file = rackup_file
|
14
|
+
@config = block || lambda { }
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
dup.call!(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
def call!(env)
|
22
|
+
@env = env
|
23
|
+
@reader, @writer = IO.pipe
|
24
|
+
|
25
|
+
if @child = fork
|
26
|
+
proceed_as_parent
|
27
|
+
else
|
28
|
+
proceed_as_child
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Stuff that happens in the parent process
|
34
|
+
|
35
|
+
def proceed_as_parent
|
36
|
+
@writer.close
|
37
|
+
rand
|
38
|
+
result, status, headers = Marshal.load(@reader)
|
39
|
+
body = Body.new(@child, @reader)
|
40
|
+
case result
|
41
|
+
when :ok
|
42
|
+
[status, headers, body]
|
43
|
+
when :error
|
44
|
+
error, backtrace = status, headers
|
45
|
+
body.close
|
46
|
+
[
|
47
|
+
500,
|
48
|
+
{'Content-Type'=>'text/html;charset=utf-8'},
|
49
|
+
[format_error(error, backtrace)]
|
50
|
+
]
|
51
|
+
else
|
52
|
+
fail "unexpected response: #{result.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Body < Struct.new(:pid, :fd)
|
57
|
+
def each
|
58
|
+
while chunk = fd.read(1024)
|
59
|
+
yield chunk
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def close
|
64
|
+
fd.close
|
65
|
+
ensure
|
66
|
+
Process.wait(pid)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_error(error, backtrace)
|
71
|
+
"<h1>Boot Error</h1>" +
|
72
|
+
"<p>Something went wrong while loading <tt>#{escape_html(rackup_file)}</tt></p>" +
|
73
|
+
"<h3>#{escape_html(error)}</h3>" +
|
74
|
+
"<pre>#{escape_html(backtrace.join("\n"))}</pre>"
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Stuff that happens in the child process
|
79
|
+
|
80
|
+
def proceed_as_child
|
81
|
+
boom = false
|
82
|
+
@reader.close
|
83
|
+
status, headers, body = assemble_app.call(@env)
|
84
|
+
Marshal.dump([:ok, status, headers.to_hash], @writer)
|
85
|
+
spec_body(body).each { |chunk| @writer.write(chunk) }
|
86
|
+
rescue Object => boom
|
87
|
+
Marshal.dump([
|
88
|
+
:error,
|
89
|
+
"#{boom.class.name}: #{boom.to_s}",
|
90
|
+
boom.backtrace
|
91
|
+
], @writer)
|
92
|
+
ensure
|
93
|
+
@writer.close
|
94
|
+
exit! boom ? 1 : 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def assemble_app
|
98
|
+
config = @config
|
99
|
+
inner_app = self.inner_app
|
100
|
+
Rack::Builder.new {
|
101
|
+
instance_eval(&config)
|
102
|
+
run inner_app
|
103
|
+
}.to_app
|
104
|
+
end
|
105
|
+
|
106
|
+
def inner_app
|
107
|
+
if rackup_file =~ /\.ru$/
|
108
|
+
config = File.read(rackup_file)
|
109
|
+
eval "Rack::Builder.new {( #{config}\n )}.to_app", nil, rackup_file
|
110
|
+
else
|
111
|
+
require rackup_file
|
112
|
+
if defined? Sinatra::Application
|
113
|
+
Sinatra::Application.set :reload, false
|
114
|
+
Sinatra::Application.set :logging, false
|
115
|
+
Sinatra::Application.set :raise_errors, true
|
116
|
+
Sinatra::Application
|
117
|
+
else
|
118
|
+
Object.const_get(File.basename(rackup_file, '.rb').capitalize)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def spec_body(body)
|
124
|
+
if body.respond_to? :to_str
|
125
|
+
[body]
|
126
|
+
elsif body.respond_to?(:each)
|
127
|
+
body
|
128
|
+
else
|
129
|
+
fail "body must respond to #each"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rack/file'
|
2
|
+
|
3
|
+
module Shotgun
|
4
|
+
# Serves static files out of the specified directory.
|
5
|
+
class Static
|
6
|
+
def initialize(app, public_dir='./public')
|
7
|
+
@file = Rack::File.new(public_dir)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
status, headers, body = @file.call(env)
|
13
|
+
if status > 400
|
14
|
+
@app.call(env)
|
15
|
+
else
|
16
|
+
[status, headers, body]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/man/index.txt
ADDED
data/man/shotgun.1.ronn
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
shotgun(1) -- reloading rack development server
|
2
|
+
===============================================
|
3
|
+
|
4
|
+
## SYNOPSIS
|
5
|
+
|
6
|
+
`shotgun` [<options>] [<rackup-file>]
|
7
|
+
|
8
|
+
## DESCRIPTION
|
9
|
+
|
10
|
+
**Shotgun** is a simple process-per-request [Rack][] server designed for use in
|
11
|
+
development environments. Each time a request is received, `shotgun` forks,
|
12
|
+
loads the <rackup-file>, processes a single request and exits. The result is
|
13
|
+
application-wide reloading of all configuration, source files, and templates
|
14
|
+
without the need for complex application-level reloading logic.
|
15
|
+
|
16
|
+
When no <rackup-file> is given, `shotgun` uses the `config.ru` file in the
|
17
|
+
current working directory.
|
18
|
+
|
19
|
+
## OPTIONS
|
20
|
+
|
21
|
+
Shotgun runs at `http://127.0.0.1:9393` by default. The following options
|
22
|
+
control server behavior:
|
23
|
+
|
24
|
+
* `-E`, `--env`=<environment>:
|
25
|
+
Sets the `RACK_ENV` environment variable to <environment> and selects
|
26
|
+
the default set of utility middleware. When <environment> is 'development',
|
27
|
+
shotgun inserts the `Rack::Lint` and `Rack::CommonLogger` middleware
|
28
|
+
components; when <environment> is 'production' or 'deployed',
|
29
|
+
`Rack::CommonLogger` is inserted; otherwise, no utility middleware are
|
30
|
+
inserted.
|
31
|
+
|
32
|
+
* `-P`, `--public`=<path>:
|
33
|
+
Serve requests for static files that exist under <path> from the shotgun
|
34
|
+
master process without forking a worker process. This option is
|
35
|
+
automatically enabled when a `./public` directory is detected, but can be
|
36
|
+
configured explicitly for non-conventional static file directory locations.
|
37
|
+
|
38
|
+
Setting this option appropriately can severely improve overall page load
|
39
|
+
times for applications with many static assets.
|
40
|
+
|
41
|
+
* `-s`, `--server`=<mongrel>|<webrick>|<thin>|<other>:
|
42
|
+
The Rack server handler implementation used to serve requests. Supported
|
43
|
+
values include: `mongrel`, `webrick`, and `thin`. By default, shotgun first
|
44
|
+
tries to use `mongrel` and falls back to `webrick` if mongrel is not
|
45
|
+
available.
|
46
|
+
|
47
|
+
* `-o`, `--host`=<addr>:
|
48
|
+
The hostname or address of the interface the HTTP server should bind to.
|
49
|
+
Default: `127.0.0.1`.
|
50
|
+
|
51
|
+
* `-p`, `--port`=<port>:
|
52
|
+
The port the HTTP server should bind to. Default: `9393`.
|
53
|
+
|
54
|
+
* `-O`, `--browse`:
|
55
|
+
Open browser at http://<host>:<port>/ immediately after the server
|
56
|
+
is started.
|
57
|
+
|
58
|
+
Ruby environment related options:
|
59
|
+
|
60
|
+
* `-e`, `--eval` <command>:
|
61
|
+
Evaluate arbitrary <command> within the Ruby interpreter. <command> is
|
62
|
+
evaluated as program arguments are parsed. Multiple `-e` arguments are
|
63
|
+
allowed.
|
64
|
+
|
65
|
+
* `-d`, `--debug`:
|
66
|
+
Turns on debug mode. `$DEBUG` will be set `true`.
|
67
|
+
|
68
|
+
* `-w`, `--warn`:
|
69
|
+
Enable verbose mode without printing version message at the beginning. It
|
70
|
+
sets the `$VERBOSE` variable to true.
|
71
|
+
|
72
|
+
* `-I`, `--include` <path>:
|
73
|
+
Add <path> to the Ruby load path (`$LOAD_PATH`). May be used more than once.
|
74
|
+
|
75
|
+
* `-r`, `--require` <library>:
|
76
|
+
Require <library> before loading the application and starting the server.
|
77
|
+
|
78
|
+
Miscellaneous:
|
79
|
+
|
80
|
+
* `-h`, `--help`:
|
81
|
+
Show usage message and exit.
|
82
|
+
|
83
|
+
* `--version`:
|
84
|
+
Show the Rack version and exit.
|
85
|
+
|
86
|
+
## INSTALLING
|
87
|
+
|
88
|
+
Shotgun is distributed as a gem package at rubygems.org:
|
89
|
+
|
90
|
+
<http://rubygems.org/gems/shotgun><br>
|
91
|
+
`gem install shotgun`
|
92
|
+
|
93
|
+
The `rack` package is required. The `mongrel` package is recommended.
|
94
|
+
|
95
|
+
## CONTRIBUTING
|
96
|
+
|
97
|
+
Fork and report issues at github.com:
|
98
|
+
|
99
|
+
<http://github.com/rtomayko/shotgun/><br>
|
100
|
+
`git clone git://github.com/rtomayko/shotgun.git`
|
101
|
+
|
102
|
+
## VERSION HISTORY
|
103
|
+
|
104
|
+
### Version 0.7 (unreleased)
|
105
|
+
|
106
|
+
* <http://github.com/rtomayko/shotgun/compare/0.6...master>
|
107
|
+
|
108
|
+
* Static files now served from the shotgun master process, making
|
109
|
+
shotgun tolerable for apps with many/unbundled static assets.
|
110
|
+
|
111
|
+
* Added `--public` (`-P`) for specifying a non-standard root / public
|
112
|
+
directory.
|
113
|
+
|
114
|
+
* Response bodies are now streamed over the master < worker pipe
|
115
|
+
instead of being marshalled. Improves performance with large response
|
116
|
+
bodies, and reduces shotgun master process RES usage.
|
117
|
+
|
118
|
+
* GET /favicon.ico requests are served an empty response by the shotgun
|
119
|
+
master process. Prevents the need to fork a worker process.
|
120
|
+
|
121
|
+
* `INT`, `TERM`, `QUIT` now properly trigger server shutdown. The second
|
122
|
+
`INT`, `TERM`, `QUIT` causes the master process to exit hard.
|
123
|
+
|
124
|
+
* Non `.ru` config files (e.g., sinatra app files) may now define command
|
125
|
+
line options in the same way as `.ru` files: by including a
|
126
|
+
`#\ -p 5555 ...` line.
|
127
|
+
|
128
|
+
### Versions < 0.7 (2009-2010)
|
129
|
+
|
130
|
+
* <http://github.com/rtomayko/shotgun/commits/0.6>
|
131
|
+
|
132
|
+
## SEE ALSO
|
133
|
+
|
134
|
+
ruby(1)
|
data/shotgun.gemspec
CHANGED
@@ -1,34 +1,40 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
|
-
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
-
|
5
2
|
s.name = 'shotgun'
|
6
|
-
s.version = '0.
|
7
|
-
s.date = '2010-
|
3
|
+
s.version = '0.7'
|
4
|
+
s.date = '2010-06-22'
|
8
5
|
|
9
|
-
s.description = "
|
6
|
+
s.description = "reloading rack development server"
|
10
7
|
s.summary = s.description
|
11
8
|
|
12
9
|
s.authors = ["Ryan Tomayko"]
|
13
|
-
s.email = "
|
10
|
+
s.email = "rtomayko@gmail.com"
|
14
11
|
|
15
12
|
s.files = %w[
|
16
|
-
README
|
17
13
|
COPYING
|
14
|
+
README
|
18
15
|
Rakefile
|
19
|
-
shotgun.gemspec
|
20
|
-
lib/shotgun.rb
|
21
16
|
bin/shotgun
|
22
|
-
|
17
|
+
lib/shotgun.rb
|
18
|
+
lib/shotgun/favicon.rb
|
19
|
+
lib/shotgun/loader.rb
|
20
|
+
lib/shotgun/static.rb
|
21
|
+
man/index.txt
|
22
|
+
man/shotgun.1.ronn
|
23
|
+
shotgun.gemspec
|
24
|
+
test/big.ru
|
25
|
+
test/boom.ru
|
26
|
+
test/slow.ru
|
27
|
+
test/test-sinatra.ru
|
23
28
|
test/test.ru
|
29
|
+
test/test_shotgun_loader.rb
|
30
|
+
test/test_shotgun_static.rb
|
24
31
|
]
|
25
32
|
s.executables = ['shotgun']
|
26
|
-
s.test_files =
|
33
|
+
s.test_files = s.files.select { |f| f =~ /test_shotgun.*rb/ }
|
27
34
|
|
28
35
|
s.extra_rdoc_files = %w[README]
|
29
|
-
s.add_dependency 'rack', '>= 0
|
36
|
+
s.add_dependency 'rack', '>= 1.0'
|
30
37
|
|
31
|
-
s.homepage = "http://github.com/
|
38
|
+
s.homepage = "http://rtomayko.github.com/shotgun/"
|
32
39
|
s.require_paths = %w[lib]
|
33
|
-
s.rubygems_version = '1.1.1'
|
34
40
|
end
|
data/test/big.ru
ADDED
data/test/boom.ru
ADDED
data/test/slow.ru
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'shotgun'
|
4
|
+
|
5
|
+
class ShotgunLoaderTest < Test::Unit::TestCase
|
6
|
+
def rackup_file(name)
|
7
|
+
"#{File.dirname(__FILE__)}/#{name}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_knows_the_rackup_file
|
11
|
+
file = rackup_file('test.ru')
|
12
|
+
shotgun = Shotgun::Loader.new(file)
|
13
|
+
assert_equal file, shotgun.rackup_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_processes_requests
|
17
|
+
file = rackup_file('test.ru')
|
18
|
+
shotgun = Shotgun::Loader.new(file)
|
19
|
+
request = Rack::MockRequest.new(shotgun)
|
20
|
+
res = request.get("/")
|
21
|
+
assert_equal 200, res.status
|
22
|
+
assert_equal "BANG!", res.body
|
23
|
+
assert_equal "text/plain", res.headers['Content-Type']
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_processes_large_requests
|
27
|
+
file = rackup_file('big.ru')
|
28
|
+
shotgun = Shotgun::Loader.new(file)
|
29
|
+
request = Rack::MockRequest.new(shotgun)
|
30
|
+
res = request.get("/")
|
31
|
+
assert_equal 200, res.status
|
32
|
+
assert res.body =~ %r|<pre>(?:.{1023}\n){1024}</pre>|,
|
33
|
+
"body of size #{res.body.size} does not match expected output"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'shotgun'
|
4
|
+
|
5
|
+
class ShotgunStaticTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@app = lambda { |env| [200,{'Content-Type'=>'text/plain'}, ['holla']] }
|
8
|
+
@public = File.dirname(__FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_serving_files
|
12
|
+
static = Shotgun::Static.new(@app, @public)
|
13
|
+
request = Rack::MockRequest.new(static)
|
14
|
+
res = request.get("/big.ru")
|
15
|
+
assert_equal 200, res.status
|
16
|
+
assert_equal File.size("#{@public}/big.ru"), res.body.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_cascading_when_file_not_found
|
20
|
+
static = Shotgun::Static.new(@app, @public)
|
21
|
+
request = Rack::MockRequest.new(static)
|
22
|
+
res = request.get("/does-not-exist")
|
23
|
+
assert_equal 200, res.status
|
24
|
+
assert_equal 'holla', res.body
|
25
|
+
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shotgun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 5
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 7
|
9
|
+
version: "0.7"
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Ryan Tomayko
|
@@ -9,21 +14,26 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-06-22 00:00:00 -07:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: rack
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
20
25
|
requirements:
|
21
26
|
- - ">="
|
22
27
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
hash: 15
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 0
|
32
|
+
version: "1.0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: reloading rack development server
|
36
|
+
email: rtomayko@gmail.com
|
27
37
|
executables:
|
28
38
|
- shotgun
|
29
39
|
extensions: []
|
@@ -31,16 +41,26 @@ extensions: []
|
|
31
41
|
extra_rdoc_files:
|
32
42
|
- README
|
33
43
|
files:
|
34
|
-
- README
|
35
44
|
- COPYING
|
45
|
+
- README
|
36
46
|
- Rakefile
|
37
|
-
- shotgun.gemspec
|
38
|
-
- lib/shotgun.rb
|
39
47
|
- bin/shotgun
|
40
|
-
-
|
48
|
+
- lib/shotgun.rb
|
49
|
+
- lib/shotgun/favicon.rb
|
50
|
+
- lib/shotgun/loader.rb
|
51
|
+
- lib/shotgun/static.rb
|
52
|
+
- man/index.txt
|
53
|
+
- man/shotgun.1.ronn
|
54
|
+
- shotgun.gemspec
|
55
|
+
- test/big.ru
|
56
|
+
- test/boom.ru
|
57
|
+
- test/slow.ru
|
58
|
+
- test/test-sinatra.ru
|
41
59
|
- test/test.ru
|
60
|
+
- test/test_shotgun_loader.rb
|
61
|
+
- test/test_shotgun_static.rb
|
42
62
|
has_rdoc: true
|
43
|
-
homepage: http://github.com/
|
63
|
+
homepage: http://rtomayko.github.com/shotgun/
|
44
64
|
licenses: []
|
45
65
|
|
46
66
|
post_install_message:
|
@@ -49,23 +69,30 @@ rdoc_options: []
|
|
49
69
|
require_paths:
|
50
70
|
- lib
|
51
71
|
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
52
73
|
requirements:
|
53
74
|
- - ">="
|
54
75
|
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
55
79
|
version: "0"
|
56
|
-
version:
|
57
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
58
82
|
requirements:
|
59
83
|
- - ">="
|
60
84
|
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
61
88
|
version: "0"
|
62
|
-
version:
|
63
89
|
requirements: []
|
64
90
|
|
65
91
|
rubyforge_project:
|
66
|
-
rubygems_version: 1.3.
|
92
|
+
rubygems_version: 1.3.7
|
67
93
|
signing_key:
|
68
|
-
specification_version:
|
69
|
-
summary:
|
94
|
+
specification_version: 3
|
95
|
+
summary: reloading rack development server
|
70
96
|
test_files:
|
71
|
-
- test/
|
97
|
+
- test/test_shotgun_loader.rb
|
98
|
+
- test/test_shotgun_static.rb
|
data/test/shotgun_test.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require 'rack/mock'
|
3
|
-
require 'shotgun'
|
4
|
-
|
5
|
-
class ShotgunTest < Test::Unit::TestCase
|
6
|
-
def setup
|
7
|
-
@rackup_file = "#{File.dirname(__FILE__)}/test.ru"
|
8
|
-
@shotgun = Shotgun.new(@rackup_file)
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_knows_the_rackup_file
|
12
|
-
assert_equal @rackup_file, @shotgun.rackup_file
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_processes_requests
|
16
|
-
request = Rack::MockRequest.new(@shotgun)
|
17
|
-
res = request.get("/")
|
18
|
-
assert_equal 200, res.status
|
19
|
-
assert_equal "BANG!", res.body
|
20
|
-
assert_equal "text/plain", res.headers['Content-Type']
|
21
|
-
end
|
22
|
-
end
|