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 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/*_test.rb']
7
+ t.test_files = FileList['test/test_shotgun_*.rb']
9
8
  t.ruby_opts = ['-rubygems'] if defined? Gem
10
9
  end
11
10
 
12
- if defined? Gem
13
- $spec = eval(File.read('shotgun.gemspec'))
14
-
15
- def package(ext='')
16
- "pkg/#{$spec.name}-#{$spec.version}#{ext}"
17
- end
18
-
19
- desc 'Build packages'
20
- task :package => %w[.gem .tar.gz].map { |ext| package(ext) }
21
-
22
- desc 'Build and install as local gem'
23
- task :install => package('.gem') do
24
- sh "gem install #{package('.gem')}"
25
- end
26
-
27
- directory 'pkg/'
28
- CLOBBER.include('pkg')
29
-
30
- file package('.gem') => %w[pkg/ shotgun.gemspec] + $spec.files do |f|
31
- sh "gem build shotgun.gemspec"
32
- mv File.basename(f.name), f.name
33
- end
34
-
35
- file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
36
- sh <<-SH
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
@@ -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
- options = {:Port => 9393, :Host => 'localhost', :AccessLog => [], :Path => '/'}
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: 0.0.0.0)") { |host|
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("-O", "--browse", "open browser immediately after starting") { |browse|
71
- browse = true
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" unless File.exist? config
87
+ abort "configuration #{config} not found" unless File.exist? config
90
88
 
91
- if config =~ /\.ru$/ && File.read(config)[/^#\\(.*)/]
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
- unless server = Rack::Handler.get(server)
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
- app_wrapper =
112
- lambda do |inner_app|
113
- case env
114
- when 'development'
115
- Rack::Builder.new {
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
- run inner_app
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
- run inner_app
125
- }.to_app
126
- else
127
- inner_app
128
- end
130
+ end
131
+ }
129
132
  end
130
133
 
131
- ENV['RACK_ENV'] = env
132
-
133
- # use the BROWSER environment variable or fall back to a more or less standard
134
- # set of commands
135
- ENV['BROWSER'] ||=
136
- %w[open xdg-open x-www-browser firefox opera mozilla netscape].find do |comm|
137
- next if comm == 'open' && `uname` !~ /Darwin/
138
- ENV['PATH'].split(':').any? { |dir| File.executable?("#{dir}/#{comm}") }
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
- require 'shotgun'
142
- app = Shotgun.new(config, app_wrapper)
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}#{options[:Path]}'"
153
+ system "#{ENV['BROWSER']} '#{base_url}'"
150
154
  else
151
155
  abort "BROWSER environment variable not set and no browser detected"
152
156
  end
@@ -1,98 +1,15 @@
1
1
  require 'rack'
2
- require 'rack/utils'
3
- require 'thread'
4
2
 
5
- class Shotgun
6
- include Rack::Utils
7
- attr_reader :rackup_file
3
+ module Shotgun
4
+ autoload :Loader, 'shotgun/loader'
5
+ autoload :SkipFavicon, 'shotgun/favicon'
6
+ autoload :Static, 'shotgun/static'
8
7
 
9
- def initialize(rackup_file, wrapper=nil)
10
- @rackup_file = rackup_file
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 call(env)
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
@@ -0,0 +1,6 @@
1
+ ruby(1) http://man.cx/ruby
2
+
3
+ # other resources
4
+ rack http://rack.rubyforge.org/doc/README.html
5
+ rack-spec http://rack.rubyforge.org/doc/SPEC.html
6
+ sinatra http://www.sinatrarb.com/
@@ -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 &lt; 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 &lt; 0.7 (2009-2010)
129
+
130
+ * <http://github.com/rtomayko/shotgun/commits/0.6>
131
+
132
+ ## SEE ALSO
133
+
134
+ ruby(1)
@@ -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.6'
7
- s.date = '2010-01-28'
3
+ s.version = '0.7'
4
+ s.date = '2010-06-22'
8
5
 
9
- s.description = "Because reloading sucks."
6
+ s.description = "reloading rack development server"
10
7
  s.summary = s.description
11
8
 
12
9
  s.authors = ["Ryan Tomayko"]
13
- s.email = "r@tomayko.com"
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
- test/shotgun_test.rb
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 = ['test/shotgun_test.rb']
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.9.1'
36
+ s.add_dependency 'rack', '>= 1.0'
30
37
 
31
- s.homepage = "http://github.com/rtomayko/shotgun/"
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
@@ -0,0 +1,15 @@
1
+ class BigResponse
2
+ def call(env)
3
+ [200, {'Content-Type'=>'text/html'}, self]
4
+ end
5
+
6
+ def each
7
+ yield "<pre>"
8
+ 1024.times do
9
+ yield(('.' * 1023) + "\n")
10
+ end
11
+ yield "</pre>"
12
+ end
13
+ end
14
+
15
+ run BigResponse.new
@@ -0,0 +1,4 @@
1
+ require 'rack'
2
+
3
+ use Rack::Lock
4
+ run lambda { |env| fail 'boom' }
@@ -0,0 +1,16 @@
1
+ class SlowResponse
2
+ def call(env)
3
+ [200, {'Content-Type'=>'text/html'}, self]
4
+ end
5
+
6
+ def each
7
+ yield "<pre>"
8
+ 10.times do
9
+ yield('.' * 10) + "\n")
10
+ sleep 0.5
11
+ end
12
+ yield "</pre>"
13
+ end
14
+ end
15
+
16
+ run SlowResponse.new
@@ -0,0 +1,14 @@
1
+ require 'sinatra'
2
+
3
+ set :logging, false
4
+
5
+ get '/' do
6
+ puts 'hello world'
7
+ "Hello World"
8
+ end
9
+
10
+ get '/boom' do
11
+ fail 'boom'
12
+ end
13
+
14
+ run Sinatra::Application
@@ -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
- version: "0.6"
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-01-28 00:00:00 -08:00
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
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
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
- version: 0.9.1
24
- version:
25
- description: Because reloading sucks.
26
- email: r@tomayko.com
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
- - test/shotgun_test.rb
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/rtomayko/shotgun/
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.5
92
+ rubygems_version: 1.3.7
67
93
  signing_key:
68
- specification_version: 2
69
- summary: Because reloading sucks.
94
+ specification_version: 3
95
+ summary: reloading rack development server
70
96
  test_files:
71
- - test/shotgun_test.rb
97
+ - test/test_shotgun_loader.rb
98
+ - test/test_shotgun_static.rb
@@ -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