shotgun 0.6 → 0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|