rack 1.1.6 → 1.6.9

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.
Files changed (212) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +1 -1
  3. data/HISTORY.md +375 -0
  4. data/KNOWN-ISSUES +23 -0
  5. data/README.rdoc +312 -0
  6. data/Rakefile +124 -0
  7. data/SPEC +125 -32
  8. data/contrib/rack.png +0 -0
  9. data/contrib/rack.svg +150 -0
  10. data/contrib/rack_logo.svg +1 -1
  11. data/contrib/rdoc.css +412 -0
  12. data/example/protectedlobster.rb +1 -1
  13. data/lib/rack/auth/abstract/handler.rb +4 -4
  14. data/lib/rack/auth/abstract/request.rb +7 -5
  15. data/lib/rack/auth/basic.rb +1 -1
  16. data/lib/rack/auth/digest/md5.rb +7 -3
  17. data/lib/rack/auth/digest/nonce.rb +1 -1
  18. data/lib/rack/auth/digest/params.rb +7 -9
  19. data/lib/rack/auth/digest/request.rb +10 -9
  20. data/lib/rack/backports/uri/common_18.rb +56 -0
  21. data/lib/rack/backports/uri/common_192.rb +52 -0
  22. data/lib/rack/backports/uri/common_193.rb +29 -0
  23. data/lib/rack/body_proxy.rb +39 -0
  24. data/lib/rack/builder.rb +106 -22
  25. data/lib/rack/cascade.rb +17 -6
  26. data/lib/rack/chunked.rb +44 -24
  27. data/lib/rack/commonlogger.rb +36 -13
  28. data/lib/rack/conditionalget.rb +49 -17
  29. data/lib/rack/config.rb +5 -0
  30. data/lib/rack/content_length.rb +14 -6
  31. data/lib/rack/content_type.rb +7 -1
  32. data/lib/rack/deflater.rb +73 -15
  33. data/lib/rack/directory.rb +18 -8
  34. data/lib/rack/etag.rb +59 -9
  35. data/lib/rack/file.rb +106 -44
  36. data/lib/rack/handler/cgi.rb +11 -11
  37. data/lib/rack/handler/fastcgi.rb +18 -6
  38. data/lib/rack/handler/lsws.rb +2 -4
  39. data/lib/rack/handler/mongrel.rb +22 -6
  40. data/lib/rack/handler/scgi.rb +16 -8
  41. data/lib/rack/handler/thin.rb +19 -4
  42. data/lib/rack/handler/webrick.rb +72 -19
  43. data/lib/rack/handler.rb +47 -14
  44. data/lib/rack/head.rb +10 -2
  45. data/lib/rack/lint.rb +260 -75
  46. data/lib/rack/lobster.rb +13 -8
  47. data/lib/rack/lock.rb +13 -3
  48. data/lib/rack/logger.rb +0 -2
  49. data/lib/rack/methodoverride.rb +27 -8
  50. data/lib/rack/mime.rb +625 -167
  51. data/lib/rack/mock.rb +78 -53
  52. data/lib/rack/multipart/generator.rb +93 -0
  53. data/lib/rack/multipart/parser.rb +253 -0
  54. data/lib/rack/multipart/uploaded_file.rb +34 -0
  55. data/lib/rack/multipart.rb +34 -0
  56. data/lib/rack/nulllogger.rb +21 -2
  57. data/lib/rack/recursive.rb +10 -5
  58. data/lib/rack/reloader.rb +3 -2
  59. data/lib/rack/request.rb +201 -74
  60. data/lib/rack/response.rb +41 -28
  61. data/lib/rack/rewindable_input.rb +15 -11
  62. data/lib/rack/runtime.rb +16 -3
  63. data/lib/rack/sendfile.rb +47 -29
  64. data/lib/rack/server.rb +223 -47
  65. data/lib/rack/session/abstract/id.rb +289 -30
  66. data/lib/rack/session/cookie.rb +133 -44
  67. data/lib/rack/session/memcache.rb +30 -56
  68. data/lib/rack/session/pool.rb +19 -43
  69. data/lib/rack/showexceptions.rb +53 -15
  70. data/lib/rack/showstatus.rb +14 -7
  71. data/lib/rack/static.rb +124 -12
  72. data/lib/rack/tempfile_reaper.rb +22 -0
  73. data/lib/rack/urlmap.rb +49 -15
  74. data/lib/rack/utils/okjson.rb +600 -0
  75. data/lib/rack/utils.rb +363 -361
  76. data/lib/rack.rb +17 -23
  77. data/rack.gemspec +11 -20
  78. data/test/builder/anything.rb +5 -0
  79. data/test/builder/comment.ru +4 -0
  80. data/test/builder/end.ru +5 -0
  81. data/test/builder/line.ru +1 -0
  82. data/test/builder/options.ru +2 -0
  83. data/test/cgi/assets/folder/test.js +1 -0
  84. data/test/cgi/assets/fonts/font.eot +1 -0
  85. data/test/cgi/assets/images/image.png +1 -0
  86. data/test/cgi/assets/index.html +1 -0
  87. data/test/cgi/assets/javascripts/app.js +1 -0
  88. data/test/cgi/assets/stylesheets/app.css +1 -0
  89. data/test/cgi/lighttpd.conf +26 -0
  90. data/test/cgi/rackup_stub.rb +6 -0
  91. data/test/cgi/sample_rackup.ru +5 -0
  92. data/test/cgi/test +9 -0
  93. data/test/cgi/test+directory/test+file +1 -0
  94. data/test/cgi/test.fcgi +8 -0
  95. data/test/cgi/test.ru +5 -0
  96. data/test/gemloader.rb +10 -0
  97. data/test/multipart/bad_robots +259 -0
  98. data/test/multipart/binary +0 -0
  99. data/test/multipart/content_type_and_no_filename +6 -0
  100. data/test/multipart/empty +10 -0
  101. data/test/multipart/fail_16384_nofile +814 -0
  102. data/test/multipart/file1.txt +1 -0
  103. data/test/multipart/filename_and_modification_param +7 -0
  104. data/test/multipart/filename_and_no_name +6 -0
  105. data/test/multipart/filename_with_escaped_quotes +6 -0
  106. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  107. data/test/multipart/filename_with_null_byte +7 -0
  108. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  109. data/test/multipart/filename_with_unescaped_percentages +6 -0
  110. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  111. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  112. data/test/multipart/filename_with_unescaped_quotes +6 -0
  113. data/test/multipart/ie +6 -0
  114. data/test/multipart/invalid_character +6 -0
  115. data/test/multipart/mixed_files +21 -0
  116. data/test/multipart/nested +10 -0
  117. data/test/multipart/none +9 -0
  118. data/test/multipart/semicolon +6 -0
  119. data/test/multipart/text +15 -0
  120. data/test/multipart/three_files_three_fields +31 -0
  121. data/test/multipart/webkit +32 -0
  122. data/test/rackup/config.ru +31 -0
  123. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  124. data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
  125. data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
  126. data/test/spec_body_proxy.rb +85 -0
  127. data/test/spec_builder.rb +223 -0
  128. data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
  129. data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
  130. data/test/spec_chunked.rb +101 -0
  131. data/test/spec_commonlogger.rb +93 -0
  132. data/test/spec_conditionalget.rb +102 -0
  133. data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
  134. data/test/spec_content_length.rb +85 -0
  135. data/test/spec_content_type.rb +45 -0
  136. data/test/spec_deflater.rb +339 -0
  137. data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
  138. data/test/spec_etag.rb +107 -0
  139. data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
  140. data/test/spec_file.rb +221 -0
  141. data/test/spec_handler.rb +72 -0
  142. data/test/spec_head.rb +45 -0
  143. data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
  144. data/test/spec_lobster.rb +58 -0
  145. data/test/spec_lock.rb +164 -0
  146. data/test/spec_logger.rb +23 -0
  147. data/test/spec_methodoverride.rb +95 -0
  148. data/test/spec_mime.rb +51 -0
  149. data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
  150. data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
  151. data/test/spec_multipart.rb +600 -0
  152. data/test/spec_nulllogger.rb +20 -0
  153. data/test/spec_recursive.rb +72 -0
  154. data/test/spec_request.rb +1227 -0
  155. data/test/spec_response.rb +407 -0
  156. data/test/spec_rewindable_input.rb +118 -0
  157. data/test/spec_runtime.rb +49 -0
  158. data/test/spec_sendfile.rb +130 -0
  159. data/test/spec_server.rb +167 -0
  160. data/test/spec_session_abstract_id.rb +53 -0
  161. data/test/spec_session_cookie.rb +410 -0
  162. data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
  163. data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
  164. data/test/spec_showexceptions.rb +85 -0
  165. data/test/spec_showstatus.rb +103 -0
  166. data/test/spec_static.rb +145 -0
  167. data/test/spec_tempfile_reaper.rb +63 -0
  168. data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
  169. data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
  170. data/test/spec_utils.rb +647 -0
  171. data/test/spec_version.rb +17 -0
  172. data/test/spec_webrick.rb +184 -0
  173. data/test/static/another/index.html +1 -0
  174. data/test/static/index.html +1 -0
  175. data/test/testrequest.rb +78 -0
  176. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  177. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  178. metadata +220 -239
  179. data/RDOX +0 -0
  180. data/README +0 -592
  181. data/lib/rack/adapter/camping.rb +0 -22
  182. data/test/spec_auth.rb +0 -57
  183. data/test/spec_rack_builder.rb +0 -84
  184. data/test/spec_rack_camping.rb +0 -55
  185. data/test/spec_rack_chunked.rb +0 -62
  186. data/test/spec_rack_commonlogger.rb +0 -61
  187. data/test/spec_rack_conditionalget.rb +0 -41
  188. data/test/spec_rack_content_length.rb +0 -43
  189. data/test/spec_rack_content_type.rb +0 -30
  190. data/test/spec_rack_deflater.rb +0 -127
  191. data/test/spec_rack_etag.rb +0 -17
  192. data/test/spec_rack_file.rb +0 -75
  193. data/test/spec_rack_handler.rb +0 -43
  194. data/test/spec_rack_head.rb +0 -30
  195. data/test/spec_rack_lobster.rb +0 -45
  196. data/test/spec_rack_lock.rb +0 -38
  197. data/test/spec_rack_logger.rb +0 -21
  198. data/test/spec_rack_methodoverride.rb +0 -60
  199. data/test/spec_rack_nulllogger.rb +0 -13
  200. data/test/spec_rack_recursive.rb +0 -77
  201. data/test/spec_rack_request.rb +0 -594
  202. data/test/spec_rack_response.rb +0 -221
  203. data/test/spec_rack_rewindable_input.rb +0 -118
  204. data/test/spec_rack_runtime.rb +0 -35
  205. data/test/spec_rack_sendfile.rb +0 -86
  206. data/test/spec_rack_session_cookie.rb +0 -92
  207. data/test/spec_rack_showexceptions.rb +0 -21
  208. data/test/spec_rack_showstatus.rb +0 -72
  209. data/test/spec_rack_static.rb +0 -37
  210. data/test/spec_rack_utils.rb +0 -557
  211. data/test/spec_rack_webrick.rb +0 -130
  212. data/test/spec_rackup.rb +0 -164
data/lib/rack/sendfile.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  require 'rack/file'
2
+ require 'rack/body_proxy'
2
3
 
3
4
  module Rack
4
- class File #:nodoc:
5
- alias :to_path :path
6
- end
7
5
 
8
6
  # = Sendfile
9
7
  #
@@ -11,7 +9,7 @@ module Rack
11
9
  # served from a file and replaces it with a server specific X-Sendfile
12
10
  # header. The web server is then responsible for writing the file contents
13
11
  # to the client. This can dramatically reduce the amount of work required
14
- # by the Ruby backend and takes advantage of the web servers optimized file
12
+ # by the Ruby backend and takes advantage of the web server's optimized file
15
13
  # delivery code.
16
14
  #
17
15
  # In order to take advantage of this middleware, the response body must
@@ -25,33 +23,33 @@ module Rack
25
23
  #
26
24
  # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
27
25
  # but requires parts of the filesystem to be mapped into a private URL
28
- # hierarachy.
26
+ # hierarchy.
29
27
  #
30
28
  # The following example shows the Nginx configuration required to create
31
29
  # a private "/files/" area, enable X-Accel-Redirect, and pass the special
32
30
  # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
33
31
  #
34
- # location /files/ {
32
+ # location ~ /files/(.*) {
35
33
  # internal;
36
- # alias /var/www/;
34
+ # alias /var/www/$1;
37
35
  # }
38
36
  #
39
37
  # location / {
40
- # proxy_redirect false;
38
+ # proxy_redirect off;
41
39
  #
42
40
  # proxy_set_header Host $host;
43
41
  # proxy_set_header X-Real-IP $remote_addr;
44
42
  # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45
43
  #
46
- # proxy_set_header X-Sendfile-Type X-Accel-Redirect
47
- # proxy_set_header X-Accel-Mapping /files/=/var/www/;
44
+ # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
45
+ # proxy_set_header X-Accel-Mapping /var/www/=/files/;
48
46
  #
49
47
  # proxy_pass http://127.0.0.1:8080/;
50
48
  # }
51
49
  #
52
- # Note that the X-Sendfile-Type header must be set exactly as shown above. The
53
- # X-Accel-Mapping header should specify the name of the private URL pattern,
54
- # followed by an equals sign (=), followed by the location on the file system
50
+ # Note that the X-Sendfile-Type header must be set exactly as shown above.
51
+ # The X-Accel-Mapping header should specify the location on the file system,
52
+ # followed by an equals sign (=), followed name of the private URL pattern
55
53
  # that it maps to. The middleware performs a simple substitution on the
56
54
  # resulting path.
57
55
  #
@@ -84,7 +82,7 @@ module Rack
84
82
  #
85
83
  # X-Sendfile is supported under Apache 2.x using a separate module:
86
84
  #
87
- # http://tn123.ath.cx/mod_xsendfile/
85
+ # https://tn123.org/mod_xsendfile/
88
86
  #
89
87
  # Once the module is compiled and installed, you can enable it using
90
88
  # XSendFile config directive:
@@ -92,13 +90,23 @@ module Rack
92
90
  # RequestHeader Set X-Sendfile-Type X-Sendfile
93
91
  # ProxyPassReverse / http://localhost:8001/
94
92
  # XSendFile on
93
+ #
94
+ # === Mapping parameter
95
+ #
96
+ # The third parameter allows for an overriding extension of the
97
+ # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
98
+ # external. The internal values may contain regular expression syntax, they
99
+ # will be matched with case indifference.
95
100
 
96
101
  class Sendfile
97
102
  F = ::File
98
103
 
99
- def initialize(app, variation=nil)
104
+ def initialize(app, variation=nil, mappings=[])
100
105
  @app = app
101
106
  @variation = variation
107
+ @mappings = mappings.map do |internal, external|
108
+ [/^#{internal}/i, external]
109
+ end
102
110
  end
103
111
 
104
112
  def call(env)
@@ -108,35 +116,45 @@ module Rack
108
116
  when 'X-Accel-Redirect'
109
117
  path = F.expand_path(body.to_path)
110
118
  if url = map_accel_path(env, path)
119
+ headers[CONTENT_LENGTH] = '0'
111
120
  headers[type] = url
112
- body = []
121
+ obody = body
122
+ body = Rack::BodyProxy.new([]) do
123
+ obody.close if obody.respond_to?(:close)
124
+ end
113
125
  else
114
- env['rack.errors'] << "X-Accel-Mapping header missing"
126
+ env['rack.errors'].puts "X-Accel-Mapping header missing"
115
127
  end
116
128
  when 'X-Sendfile', 'X-Lighttpd-Send-File'
117
129
  path = F.expand_path(body.to_path)
130
+ headers[CONTENT_LENGTH] = '0'
118
131
  headers[type] = path
119
- body = []
132
+ obody = body
133
+ body = Rack::BodyProxy.new([]) do
134
+ obody.close if obody.respond_to?(:close)
135
+ end
120
136
  when '', nil
121
137
  else
122
- env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
138
+ env['rack.errors'].puts "Unknown x-sendfile variation: '#{type}'.\n"
123
139
  end
124
140
  end
125
141
  [status, headers, body]
126
142
  end
127
143
 
128
144
  private
129
- def variation(env)
130
- @variation ||
131
- env['sendfile.type'] ||
132
- env['HTTP_X_SENDFILE_TYPE']
133
- end
145
+ def variation(env)
146
+ @variation ||
147
+ env['sendfile.type'] ||
148
+ env['HTTP_X_SENDFILE_TYPE']
149
+ end
134
150
 
135
- def map_accel_path(env, file)
136
- if mapping = env['HTTP_X_ACCEL_MAPPING']
137
- internal, external = mapping.split('=', 2).map{ |p| p.strip }
138
- file.sub(/^#{internal}/i, external)
139
- end
151
+ def map_accel_path(env, path)
152
+ if mapping = @mappings.find { |internal,_| internal =~ path }
153
+ path.sub(*mapping)
154
+ elsif mapping = env['HTTP_X_ACCEL_MAPPING']
155
+ internal, external = mapping.split('=', 2).map{ |p| p.strip }
156
+ path.sub(/^#{internal}/i, external)
140
157
  end
158
+ end
141
159
  end
142
160
  end
data/lib/rack/server.rb CHANGED
@@ -1,7 +1,11 @@
1
1
  require 'optparse'
2
+ require 'fileutils'
3
+
2
4
 
3
5
  module Rack
6
+
4
7
  class Server
8
+
5
9
  class Options
6
10
  def parse!(args)
7
11
  options = {}
@@ -17,16 +21,23 @@ module Rack
17
21
  lineno += 1
18
22
  }
19
23
 
24
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
25
+ options[:builder] = line
26
+ }
27
+
20
28
  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
21
29
  options[:debug] = true
22
30
  }
23
31
  opts.on("-w", "--warn", "turn warnings on for your script") {
24
32
  options[:warn] = true
25
33
  }
34
+ opts.on("-q", "--quiet", "turn off logging") {
35
+ options[:quiet] = true
36
+ }
26
37
 
27
38
  opts.on("-I", "--include PATH",
28
39
  "specify $LOAD_PATH (may be used more than once)") { |path|
29
- options[:include] = path.split(":")
40
+ (options[:include] ||= []).concat(path.split(":"))
30
41
  }
31
42
 
32
43
  opts.on("-r", "--require LIBRARY",
@@ -36,11 +47,11 @@ module Rack
36
47
 
37
48
  opts.separator ""
38
49
  opts.separator "Rack options:"
39
- opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
50
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
40
51
  options[:server] = s
41
52
  }
42
53
 
43
- opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
54
+ opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
44
55
  options[:Host] = host
45
56
  }
46
57
 
@@ -48,6 +59,12 @@ module Rack
48
59
  options[:Port] = port
49
60
  }
50
61
 
62
+ opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
63
+ name, value = name.split('=', 2)
64
+ value = true if value.nil?
65
+ options[name.to_sym] = value
66
+ }
67
+
51
68
  opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
52
69
  options[:environment] = e
53
70
  }
@@ -56,37 +73,118 @@ module Rack
56
73
  options[:daemonize] = d ? true : false
57
74
  }
58
75
 
59
- opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
60
- options[:pid] = ::File.expand_path(f, Dir.pwd)
76
+ opts.on("-P", "--pid FILE", "file to store PID") { |f|
77
+ options[:pid] = ::File.expand_path(f)
61
78
  }
62
79
 
63
80
  opts.separator ""
64
81
  opts.separator "Common options:"
65
82
 
66
- opts.on_tail("-h", "--help", "Show this message") do
83
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
67
84
  puts opts
85
+ puts handler_opts(options)
86
+
68
87
  exit
69
88
  end
70
89
 
71
90
  opts.on_tail("--version", "Show version") do
72
- puts "Rack #{Rack.version}"
91
+ puts "Rack #{Rack.version} (Release: #{Rack.release})"
73
92
  exit
74
93
  end
75
94
  end
76
- opt_parser.parse! args
95
+
96
+ begin
97
+ opt_parser.parse! args
98
+ rescue OptionParser::InvalidOption => e
99
+ warn e.message
100
+ abort opt_parser.to_s
101
+ end
102
+
77
103
  options[:config] = args.last if args.last
78
104
  options
79
105
  end
80
- end
81
106
 
82
- def self.start
83
- new.start
107
+ def handler_opts(options)
108
+ begin
109
+ info = []
110
+ server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
111
+ if server && server.respond_to?(:valid_options)
112
+ info << ""
113
+ info << "Server-specific options for #{server.name}:"
114
+
115
+ has_options = false
116
+ server.valid_options.each do |name, description|
117
+ next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
118
+ info << " -O %-21s %s" % [name, description]
119
+ has_options = true
120
+ end
121
+ return "" if !has_options
122
+ end
123
+ info.join("\n")
124
+ rescue NameError
125
+ return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
126
+ end
127
+ end
84
128
  end
85
129
 
86
- attr_accessor :options
130
+ # Start a new rack server (like running rackup). This will parse ARGV and
131
+ # provide standard ARGV rackup options, defaulting to load 'config.ru'.
132
+ #
133
+ # Providing an options hash will prevent ARGV parsing and will not include
134
+ # any default options.
135
+ #
136
+ # This method can be used to very easily launch a CGI application, for
137
+ # example:
138
+ #
139
+ # Rack::Server.start(
140
+ # :app => lambda do |e|
141
+ # [200, {'Content-Type' => 'text/html'}, ['hello world']]
142
+ # end,
143
+ # :server => 'cgi'
144
+ # )
145
+ #
146
+ # Further options available here are documented on Rack::Server#initialize
147
+ def self.start(options = nil)
148
+ new(options).start
149
+ end
87
150
 
151
+ attr_writer :options
152
+
153
+ # Options may include:
154
+ # * :app
155
+ # a rack application to run (overrides :config)
156
+ # * :config
157
+ # a rackup configuration file path to load (.ru)
158
+ # * :environment
159
+ # this selects the middleware that will be wrapped around
160
+ # your application. Default options available are:
161
+ # - development: CommonLogger, ShowExceptions, and Lint
162
+ # - deployment: CommonLogger
163
+ # - none: no extra middleware
164
+ # note: when the server is a cgi server, CommonLogger is not included.
165
+ # * :server
166
+ # choose a specific Rack::Handler, e.g. cgi, fcgi, webrick
167
+ # * :daemonize
168
+ # if true, the server will daemonize itself (fork, detach, etc)
169
+ # * :pid
170
+ # path to write a pid file after daemonize
171
+ # * :Host
172
+ # the host address to bind to (used by supporting Rack::Handler)
173
+ # * :Port
174
+ # the port to bind to (used by supporting Rack::Handler)
175
+ # * :AccessLog
176
+ # webrick access log options (or supporting Rack::Handler)
177
+ # * :debug
178
+ # turn on debug output ($DEBUG = true)
179
+ # * :warn
180
+ # turn on warnings ($-w = true)
181
+ # * :include
182
+ # add given paths to $LOAD_PATH
183
+ # * :require
184
+ # require the given libraries
88
185
  def initialize(options = nil)
89
186
  @options = options
187
+ @app = options[:app] if options && options[:app]
90
188
  end
91
189
 
92
190
  def options
@@ -94,80 +192,130 @@ module Rack
94
192
  end
95
193
 
96
194
  def default_options
195
+ environment = ENV['RACK_ENV'] || 'development'
196
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
197
+
97
198
  {
98
- :environment => "development",
199
+ :environment => environment,
99
200
  :pid => nil,
100
201
  :Port => 9292,
101
- :Host => "0.0.0.0",
202
+ :Host => default_host,
102
203
  :AccessLog => [],
103
- :config => ::File.expand_path("config.ru", Dir.pwd)
204
+ :config => "config.ru"
104
205
  }
105
206
  end
106
207
 
107
208
  def app
108
- @app ||= begin
109
- if !::File.exist? options[:config]
110
- abort "configuration #{options[:config]} not found"
111
- end
209
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
210
+ end
112
211
 
113
- app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
114
- self.options.merge! options
115
- app
212
+ class << self
213
+ def logging_middleware
214
+ lambda { |server|
215
+ server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
216
+ }
116
217
  end
117
- end
118
218
 
119
- def self.middleware
120
- @middleware ||= begin
219
+ def default_middleware_by_environment
121
220
  m = Hash.new {|h,k| h[k] = []}
122
- m["deployment"].concat [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
123
- m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
221
+ m["deployment"] = [
222
+ [Rack::ContentLength],
223
+ [Rack::Chunked],
224
+ logging_middleware,
225
+ [Rack::TempfileReaper]
226
+ ]
227
+ m["development"] = [
228
+ [Rack::ContentLength],
229
+ [Rack::Chunked],
230
+ logging_middleware,
231
+ [Rack::ShowExceptions],
232
+ [Rack::Lint],
233
+ [Rack::TempfileReaper]
234
+ ]
235
+
124
236
  m
125
237
  end
238
+
239
+ def middleware
240
+ default_middleware_by_environment
241
+ end
126
242
  end
127
243
 
128
244
  def middleware
129
245
  self.class.middleware
130
246
  end
131
247
 
132
- def start
133
- if options[:debug]
134
- $DEBUG = true
135
- require 'pp'
136
- p options[:server]
137
- pp wrapped_app
138
- pp app
139
- end
140
-
248
+ def start &blk
141
249
  if options[:warn]
142
250
  $-w = true
143
251
  end
144
252
 
145
253
  if includes = options[:include]
146
- $LOAD_PATH.unshift *includes
254
+ $LOAD_PATH.unshift(*includes)
147
255
  end
148
256
 
149
257
  if library = options[:require]
150
258
  require library
151
259
  end
152
260
 
261
+ if options[:debug]
262
+ $DEBUG = true
263
+ require 'pp'
264
+ p options[:server]
265
+ pp wrapped_app
266
+ pp app
267
+ end
268
+
269
+ check_pid! if options[:pid]
270
+
271
+ # Touch the wrapped app, so that the config.ru is loaded before
272
+ # daemonization (i.e. before chdir, etc).
273
+ wrapped_app
274
+
153
275
  daemonize_app if options[:daemonize]
276
+
154
277
  write_pid if options[:pid]
155
- server.run wrapped_app, options
278
+
279
+ trap(:INT) do
280
+ if server.respond_to?(:shutdown)
281
+ server.shutdown
282
+ else
283
+ exit
284
+ end
285
+ end
286
+
287
+ server.run wrapped_app, options, &blk
156
288
  end
157
289
 
158
290
  def server
159
- @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default
291
+ @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
160
292
  end
161
293
 
162
294
  private
295
+ def build_app_and_options_from_config
296
+ if !::File.exist? options[:config]
297
+ abort "configuration #{options[:config]} not found"
298
+ end
299
+
300
+ app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
301
+ self.options.merge! options
302
+ app
303
+ end
304
+
305
+ def build_app_from_string
306
+ Rack::Builder.new_from_string(self.options[:builder])
307
+ end
308
+
163
309
  def parse_options(args)
164
310
  options = default_options
165
311
 
166
312
  # Don't evaluate CGI ISINDEX parameters.
167
- # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
168
- args.clear if ENV.include?("REQUEST_METHOD")
313
+ # http://www.meb.uni-bonn.de/docs/cgi/cl.html
314
+ args.clear if ENV.include?(REQUEST_METHOD)
169
315
 
170
- options.merge! opt_parser.parse! args
316
+ options.merge! opt_parser.parse!(args)
317
+ options[:config] = ::File.expand_path(options[:config])
318
+ ENV["RACK_ENV"] = options[:environment]
171
319
  options
172
320
  end
173
321
 
@@ -179,8 +327,8 @@ module Rack
179
327
  middleware[options[:environment]].reverse_each do |middleware|
180
328
  middleware = middleware.call(self) if middleware.respond_to?(:call)
181
329
  next unless middleware
182
- klass = middleware.shift
183
- app = klass.new(app, *middleware)
330
+ klass, *args = middleware
331
+ app = klass.new(app, *args)
184
332
  end
185
333
  app
186
334
  end
@@ -195,7 +343,6 @@ module Rack
195
343
  Process.setsid
196
344
  exit if fork
197
345
  Dir.chdir "/"
198
- ::File.umask 0000
199
346
  STDIN.reopen "/dev/null"
200
347
  STDOUT.reopen "/dev/null", "a"
201
348
  STDERR.reopen "/dev/null", "a"
@@ -205,8 +352,37 @@ module Rack
205
352
  end
206
353
 
207
354
  def write_pid
208
- ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
209
- at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
355
+ ::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") }
356
+ at_exit { ::FileUtils.rm_f(options[:pid]) }
357
+ rescue Errno::EEXIST
358
+ check_pid!
359
+ retry
210
360
  end
361
+
362
+ def check_pid!
363
+ case pidfile_process_status
364
+ when :running, :not_owned
365
+ $stderr.puts "A server is already running. Check #{options[:pid]}."
366
+ exit(1)
367
+ when :dead
368
+ ::File.delete(options[:pid])
369
+ end
370
+ end
371
+
372
+ def pidfile_process_status
373
+ return :exited unless ::File.exist?(options[:pid])
374
+
375
+ pid = ::File.read(options[:pid]).to_i
376
+ return :dead if pid == 0
377
+
378
+ Process.kill(0, pid)
379
+ :running
380
+ rescue Errno::ESRCH
381
+ :dead
382
+ rescue Errno::EPERM
383
+ :not_owned
384
+ end
385
+
211
386
  end
387
+
212
388
  end