rack 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

@@ -22,8 +22,12 @@ module Rack
22
22
  instance_eval(&block)
23
23
  end
24
24
 
25
- def use(middleware, *args)
26
- @ins << lambda { |app| middleware.new(app, *args) }
25
+ def use(middleware, *args, &block)
26
+ @ins << if block_given?
27
+ lambda { |app| middleware.new(app, *args, &block) }
28
+ else
29
+ lambda { |app| middleware.new(app, *args) }
30
+ end
27
31
  end
28
32
 
29
33
  def run(app)
@@ -41,8 +45,8 @@ module Rack
41
45
 
42
46
  def to_app
43
47
  @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
44
- inner_app = @ins.pop
45
- @ins.reverse.inject(inner_app) { |a, e| e.call(a) }
48
+ inner_app = @ins.last
49
+ @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
46
50
  end
47
51
 
48
52
  def call(env)
@@ -24,11 +24,12 @@ module Rack
24
24
  return [403, {"Content-Type" => "text/plain"}, ["Forbidden\n"]]
25
25
  end
26
26
 
27
- @path = F.join(@root, env["PATH_INFO"])
27
+ @path = F.join(@root, Utils.unescape(env["PATH_INFO"]))
28
28
  ext = F.extname(@path)[1..-1]
29
29
 
30
30
  if F.file?(@path) && F.readable?(@path)
31
31
  [200, {
32
+ "Last-Modified" => F.mtime(@path).rfc822,
32
33
  "Content-Type" => MIME_TYPES[ext] || "text/plain",
33
34
  "Content-Length" => F.size(@path).to_s
34
35
  }, self]
@@ -72,6 +73,7 @@ module Rack
72
73
  "jpe" => "image/jpeg",
73
74
  "jpeg" => "image/jpeg",
74
75
  "jpg" => "image/jpeg",
76
+ "js" => "text/javascript",
75
77
  "lha" => "application/octet-stream",
76
78
  "lzh" => "application/octet-stream",
77
79
  "mov" => "video/quicktime",
@@ -3,7 +3,9 @@ require 'fcgi'
3
3
  module Rack
4
4
  module Handler
5
5
  class FastCGI
6
- def self.run(app, options=nil)
6
+ def self.run(app, options={})
7
+ file = options[:File] and STDIN.reopen(UNIXServer.new(file))
8
+ port = options[:Port] and STDIN.reopen(TCPServer.new(port))
7
9
  FCGI.each { |request|
8
10
  serve request, app
9
11
  }
@@ -0,0 +1,52 @@
1
+ require 'lsapi'
2
+ #require 'cgi'
3
+ module Rack
4
+ module Handler
5
+ class LSWS
6
+ def self.run(app, options=nil)
7
+ while LSAPI.accept != nil
8
+ serve app
9
+ end
10
+ end
11
+ def self.serve(app)
12
+ env = ENV.to_hash
13
+ env.delete "HTTP_CONTENT_LENGTH"
14
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
15
+ env.update({"rack.version" => [0,1],
16
+ "rack.input" => STDIN,
17
+ "rack.errors" => STDERR,
18
+ "rack.multithread" => false,
19
+ "rack.multiprocess" => true,
20
+ "rack.run_once" => false,
21
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
22
+ })
23
+ env["QUERY_STRING"] ||= ""
24
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
25
+ env["REQUEST_PATH"] ||= "/"
26
+ status, headers, body = app.call(env)
27
+ begin
28
+ send_headers status, headers
29
+ send_body body
30
+ ensure
31
+ body.close if body.respond_to? :close
32
+ end
33
+ end
34
+ def self.send_headers(status, headers)
35
+ print "Status: #{status}\r\n"
36
+ headers.each { |k, vs|
37
+ vs.each { |v|
38
+ print "#{k}: #{v}\r\n"
39
+ }
40
+ }
41
+ print "\r\n"
42
+ STDOUT.flush
43
+ end
44
+ def self.send_body(body)
45
+ body.each { |part|
46
+ print part
47
+ STDOUT.flush
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ require 'scgi'
2
+ require 'stringio'
3
+
4
+ module Rack
5
+ module Handler
6
+ class SCGI < ::SCGI::Processor
7
+ attr_accessor :app
8
+
9
+ def self.run(app, options=nil)
10
+ new(options.merge(:app=>app,
11
+ :host=>options[:Host],
12
+ :port=>options[:Port],
13
+ :socket=>options[:Socket])).listen
14
+ end
15
+
16
+ def initialize(settings = {})
17
+ @app = settings[:app]
18
+ @log = Object.new
19
+ def @log.info(*args); end
20
+ def @log.error(*args); end
21
+ super(settings)
22
+ end
23
+
24
+ def process_request(request, input_body, socket)
25
+ env = {}.replace(request)
26
+ env.delete "HTTP_CONTENT_TYPE"
27
+ env.delete "HTTP_CONTENT_LENGTH"
28
+ env["REQUEST_PATH"], env["QUERY_STRING"] = env["REQUEST_URI"].split('?', 2)
29
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
30
+ env["PATH_INFO"] = env["REQUEST_PATH"]
31
+ env["QUERY_STRING"] ||= ""
32
+ env["SCRIPT_NAME"] = ""
33
+ env.update({"rack.version" => [0,1],
34
+ "rack.input" => StringIO.new(input_body),
35
+ "rack.errors" => STDERR,
36
+
37
+ "rack.multithread" => true,
38
+ "rack.multiprocess" => true,
39
+ "rack.run_once" => false,
40
+
41
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
42
+ })
43
+ status, headers, body = app.call(env)
44
+ begin
45
+ socket.write("Status: #{status}\r\n")
46
+ headers.each do |k, vs|
47
+ vs.each {|v| socket.write("#{k}: #{v}\r\n")}
48
+ end
49
+ socket.write("\r\n")
50
+ body.each {|s| socket.write(s)}
51
+ ensure
52
+ body.close if body.respond_to? :close
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -158,7 +158,7 @@ module Rack
158
158
  ## * There must be a valid error stream in <tt>rack.errors</tt>.
159
159
  check_error env["rack.errors"]
160
160
 
161
- ## * The <tt>REQUEST_METHOD</tt> must be one of +GET+, +POST+,
161
+ ## * The <tt>REQUEST_METHOD</tt> must be one of +GET+, +POST+, +PUT+,
162
162
  ## +DELETE+, +HEAD+, +OPTIONS+, +TRACE+.
163
163
  assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
164
164
  %w[GET POST PUT DELETE
@@ -226,11 +226,11 @@ module Rack
226
226
  ## and return a string, or +nil+ on EOF.
227
227
  def read(*args)
228
228
  assert("rack.input#read called with too many arguments") {
229
- args.size < 1
229
+ args.size <= 1
230
230
  }
231
231
  if args.size == 1
232
232
  assert("rack.input#read called with non-integer argument") {
233
- args.first.instance_of? Integer
233
+ args.first.kind_of? Integer
234
234
  }
235
235
  end
236
236
  v = @input.read(*args)
@@ -343,11 +343,11 @@ module Rack
343
343
  def check_content_type(status, headers)
344
344
  headers.each { |key, value|
345
345
  ## There must be a <tt>Content-Type</tt>, except when the
346
- ## +Status+ is 201, 204, 304, in which case there must be none
346
+ ## +Status+ is 204 or 304, in which case there must be none
347
347
  ## given.
348
348
  if key.downcase == "content-type"
349
349
  assert("Content-Type header found in #{status} response, not allowed"){
350
- not [201, 204, 304].include? status.to_i
350
+ not [204, 304].include? status.to_i
351
351
  }
352
352
  return
353
353
  end
@@ -101,9 +101,16 @@ module Rack
101
101
  @env["rack.request.cookie_hash"]
102
102
  else
103
103
  @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
104
- # XXX sure?
104
+ # According to RFC 2109:
105
+ # If multiple cookies satisfy the criteria above, they are ordered in
106
+ # the Cookie header such that those with more specific Path attributes
107
+ # precede those with less specific. Ordering with respect to other
108
+ # attributes (e.g., Domain) is unspecified.
105
109
  @env["rack.request.cookie_hash"] =
106
- Utils.parse_query(@env["rack.request.cookie_string"], ';,')
110
+ Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)|
111
+ h[k] = Array === v ? v.first : v
112
+ h
113
+ }
107
114
  end
108
115
  end
109
116
 
@@ -55,8 +55,10 @@ module Rack
55
55
  when Hash
56
56
  domain = "; domain=" + value[:domain] if value[:domain]
57
57
  path = "; path=" + value[:path] if value[:path]
58
+ # According to RFC 2109, we need dashes here.
59
+ # N.B.: cgi.rb uses spaces...
58
60
  expires = "; expires=" + value[:expires].clone.gmtime.
59
- strftime("%a, %d %b %Y %H:%M:%S GMT") if value[:expires]
61
+ strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
60
62
  value = value[:value]
61
63
  end
62
64
  value = [value] unless Array === value
@@ -76,7 +78,7 @@ module Rack
76
78
 
77
79
  def delete_cookie(key, value={})
78
80
  unless Array === self["Set-Cookie"]
79
- self["Set-Cookie"] = [self["Set-Cookie"]]
81
+ self["Set-Cookie"] = [self["Set-Cookie"]].compact
80
82
  end
81
83
 
82
84
  self["Set-Cookie"].reject! { |cookie|
@@ -92,7 +94,7 @@ module Rack
92
94
  def finish(&block)
93
95
  @block = block
94
96
 
95
- if [201, 204, 304].include?(status.to_i)
97
+ if [204, 304].include?(status.to_i)
96
98
  header.delete "Content-Type"
97
99
  [status.to_i, header.to_hash, []]
98
100
  else
@@ -112,6 +114,10 @@ module Rack
112
114
  str
113
115
  end
114
116
 
117
+ def close
118
+ body.close if body.respond_to?(:close)
119
+ end
120
+
115
121
  def empty?
116
122
  @block == nil && @body.empty?
117
123
  end
@@ -1,5 +1,3 @@
1
- require 'base64'
2
-
3
1
  module Rack
4
2
 
5
3
  module Session
@@ -40,7 +38,7 @@ module Rack
40
38
  session_data = request.cookies[@key]
41
39
 
42
40
  begin
43
- session_data = Base64.decode64(session_data)
41
+ session_data = session_data.unpack("m*").first
44
42
  session_data = Marshal.load(session_data)
45
43
  env["rack.session"] = session_data
46
44
  rescue
@@ -52,7 +50,7 @@ module Rack
52
50
 
53
51
  def commit_session(env, status, headers, body)
54
52
  session_data = Marshal.dump(env["rack.session"])
55
- session_data = Base64.encode64(session_data)
53
+ session_data = [session_data].pack("m*")
56
54
 
57
55
  if session_data.size > (4096 - @key.size)
58
56
  env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
@@ -0,0 +1,82 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+
3
+ module Rack
4
+ module Session
5
+ # Rack::Session::Pool provides simple cookie based session management.
6
+ # Session data is stored in a hash held by @pool. The corresponding
7
+ # session key sent to the client.
8
+ # The pool is unmonitored and unregulated, which means that over
9
+ # prolonged use the session pool will be very large.
10
+ #
11
+ # Example:
12
+ #
13
+ # use Rack::Session::Pool, :key => 'rack.session',
14
+ # :domain => 'foo.com',
15
+ # :path => '/',
16
+ # :expire_after => 2592000
17
+ #
18
+ # All parameters are optional.
19
+
20
+ class Pool
21
+ attr_reader :pool, :key
22
+
23
+ def initialize(app, options={})
24
+ @app = app
25
+ @key = options[:key] || "rack.session"
26
+ @default_options = {:domain => nil,
27
+ :path => "/",
28
+ :expire_after => nil}.merge(options)
29
+ @pool = Hash.new
30
+ @default_context = context app, &nil
31
+ end
32
+
33
+
34
+ def call(env)
35
+ @default_context.call(env)
36
+ end
37
+
38
+ def context(app, &block)
39
+ Rack::Utils::Context.new self, app do |env|
40
+ load_session env
41
+ block[env] if block
42
+ response = app.call(env)
43
+ commit_session env, response
44
+ response
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def load_session(env)
51
+ sess_id = env.fetch('HTTP_COOKIE','')[/#{@key}=([^,;]+)/,1]
52
+ begin
53
+ sess_id = Array.new(8){rand(16).to_s(16)}*''
54
+ end while @pool.key? sess_id if sess_id.nil? or !@pool.key? sess_id
55
+
56
+ session = @pool.fetch sess_id, {}
57
+ session.instance_variable_set '@dat', [sess_id, Time.now]
58
+
59
+ @pool.store sess_id, env['rack.session'] = session
60
+ env["rack.session.options"] = @default_options.dup
61
+ end
62
+
63
+ def commit_session(env, response)
64
+ session = env['rack.session']
65
+ options = env['rack.session.options']
66
+ sdat = session.instance_variable_get '@dat'
67
+
68
+ cookie = Utils.escape(@key)+'='+Utils.escape(sdat[0])
69
+ cookie<< "; domain=#{options[:domain]}" if options[:domain]
70
+ cookie<< "; path=#{options[:path]}" if options[:path]
71
+ cookie<< "; expires=#{sdat[1]+options[:expires_after]}" if options[:expires_after]
72
+
73
+ case a = (h = response[1])['Set-Cookie']
74
+ when Array then a << cookie
75
+ when String then h['Set-Cookie'] = [a, cookie]
76
+ when nil then h['Set-Cookie'] = cookie
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -217,7 +217,7 @@ TEMPLATE = <<'HTML'
217
217
  <% if frame.pre_context %>
218
218
  <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
219
219
  <% frame.pre_context.each { |line| %>
220
- <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>"><%=h line %></li>
220
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
221
221
  <% } %>
222
222
  </ol>
223
223
  <% end %>
@@ -28,15 +28,13 @@ module Rack
28
28
 
29
29
  def call(env)
30
30
  path = env["PATH_INFO"].to_s.squeeze("/")
31
+ hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
31
32
  @mapping.each { |host, location, app|
32
- if (env["HTTP_HOST"] == host ||
33
- env["SERVER_NAME"] == host ||
34
- (host == nil && (env["HTTP_HOST"] == env["SERVER_NAME"] ||
35
- env["HTTP_HOST"] ==
36
- "#{env["SERVER_NAME"]}:#{env["SERVER_PORT"]}"))) &&
37
- location == path[0, location.size] && (path[location.size] == nil ||
38
- path[location.size] == ?/)
39
- env["SCRIPT_NAME"] = location.dup
33
+ if (hHost == host || sName == host \
34
+ || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) \
35
+ and location == path[0, location.size] \
36
+ and (path[location.size] == nil || path[location.size] == ?/)
37
+ env["SCRIPT_NAME"] += location.dup
40
38
  env["PATH_INFO"] = path[location.size..-1]
41
39
  env["PATH_INFO"].gsub!(/\/\z/, '')
42
40
  env["PATH_INFO"] = "/" if env["PATH_INFO"].empty?
@@ -58,6 +58,27 @@ module Rack
58
58
  end
59
59
  module_function :escape_html
60
60
 
61
+ class Context < Proc
62
+ def initialize app_f=nil, app_r=nil
63
+ @for, @app = app_f, app_r
64
+ end
65
+ alias_method :old_inspect, :inspect
66
+ def inspect
67
+ "#{old_inspect} ==> #{@for.inspect} ==> #{@app.inspect}"
68
+ end
69
+ def pretty_print pp
70
+ pp.text old_inspect
71
+ pp.nest 1 do
72
+ pp.breakable
73
+ pp.text '=for> '
74
+ pp.pp @for
75
+ pp.breakable
76
+ pp.text '=app> '
77
+ pp.pp @app
78
+ end
79
+ end
80
+ end
81
+
61
82
  # A case-normalizing Hash, adjusting on [] and []=.
62
83
  class HeaderHash < Hash
63
84
  def initialize(hash={})
@@ -5,7 +5,7 @@ server.port = 9203
5
5
 
6
6
  server.event-handler = "freebsd-kqueue"
7
7
 
8
- cgi.assign = ("/test" => "/usr/local/bin/ruby",
8
+ cgi.assign = ("/test" => "/usr/bin/ruby",
9
9
  # ".ru" => ""
10
10
  )
11
11
 
@@ -1,4 +1,4 @@
1
- #!/usr/local/bin/ruby ../../bin/rackup
1
+ #!/usr/bin/env ruby ../../bin/rackup
2
2
  #\ -E deployment -I ~/projects/rack/lib
3
3
  # -*- ruby -*-
4
4