shotgun 0.6 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
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