Permalink
Browse files

Add Rack::Response and Rack::Utils

darcs-hash:20070216163453-4fc50-0d0fcd15fe7bcc67d3a317ddba810caee004a49e.gz
  • Loading branch information...
1 parent 5f13e3d commit 7ed819ad716661a67ada21d04e7815e493ccdb9d @chneukirchen chneukirchen committed Feb 16, 2007
Showing with 250 additions and 44 deletions.
  1. +2 −0 lib/rack.rb
  2. +5 −44 lib/rack/request.rb
  3. +78 −0 lib/rack/response.rb
  4. +68 −0 lib/rack/utils.rb
  5. +65 −0 test/spec_rack_response.rb
  6. +32 −0 test/spec_rack_utils.rb
View
2 lib/rack.rb
@@ -7,8 +7,10 @@ def self.version
autoload :Lint, "rack/lint"
autoload :File, "rack/file"
+ autoload :Utils, "rack/utils"
autoload :Request, "rack/request"
+ autoload :Response, "rack/response"
module Handler
autoload :CGI, "rack/handler/cgi"
View
49 lib/rack/request.rb
@@ -1,3 +1,5 @@
+require 'rack/utils'
+
module Rack
class Request
def initialize(env)
@@ -14,65 +16,24 @@ def path_info; @env["PATH_INFO"].to_s end
def port; @env["SERVER_PORT"].to_i end
def GET
- parse_query(@env["QUERY_STRING"])
+ Utils.parse_query(@env["QUERY_STRING"])
end
def POST
@env["rack.request.formvars"] ||= body.read
- parse_query(@env["rack.request.formvars"])
+ Utils.parse_query(@env["rack.request.formvars"])
end
def params
self.GET.update(self.POST)
end
def cookies
- parse_query(@env["HTTP_COOKIE"], ';,')
+ Utils.parse_query(@env["HTTP_COOKIE"], ';,') # XXX sure?
end
def xhr?
@env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
end
-
-
- # Performs URI escaping so that you can construct proper
- # query strings faster. Use this rather than the cgi.rb
- # version since it's faster. (Stolen from Camping).
- def escape(s)
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
- '%'+$1.unpack('H2'*$1.size).join('%').upcase
- }.tr(' ', '+')
- end
-
- # Unescapes a URI escaped string. (Stolen from Camping).
- def unescape(s)
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
- [$1.delete('%')].pack('H*')
- }
- end
-
- # Stolen from Mongrel:
- # Parses a query string by breaking it up at the '&'
- # and ';' characters. You can also use this to parse
- # cookies by changing the characters used in the second
- # parameter (which defaults to '&;'.
-
- def parse_query(qs, d = '&;')
- params = {}
- (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
- k, v=unescape(p).split('=',2)
- if cur = params[k]
- if cur.class == Array
- params[k] << v
- else
- params[k] = [cur, v]
- end
- else
- params[k] = v
- end
- }
-
- return params
- end
end
end
View
78 lib/rack/response.rb
@@ -0,0 +1,78 @@
+require 'rack/request'
+require 'rack/utils'
+
+module Rack
+ class Response
+ def initialize
+ @status = 200
+ @header = Utils::HeaderHash.new({"Content-Type" => "text/html"})
+ @body = []
+
+ @writer = lambda { |x| @body << x }
+ end
+
+ attr_reader :status, :header, :body
+
+ def [](key)
+ header[key]
+ end
+
+ def []=(key, value)
+ header[key] = value
+ end
+
+ def set_cookie(key, value)
+ case value
+ when Hash
+ domain = "; domain=" + value[:domain] if value[:domain]
+ path = "; path=" + value[:path] if value[:path]
+ expires = "; expires=" + value[:expires].clone.gmtime.
+ strftime("%a, %d %b %Y %H:%M:%S GMT") if value[:expires]
+ value = value[:value]
+ end
+ value = [value] unless Array === value
+ cookie = Utils.escape(key) + "=" +
+ value.map { |v| Utils.escape v }.join("&") +
+ "#{domain}#{path}#{expires}"
+
+ case self["Set-Cookie"]
+ when Array
+ self["Set-Cookie"] << cookie
+ when String
+ self["Set-Cookie"] = [self["Set-Cookie"], cookie]
+ when nil
+ self["Set-Cookie"] = cookie
+ end
+ end
+
+ def delete_cookie(key, value={})
+ unless Array === self["Set-Cookie"]
+ self["Set-Cookie"] = [self["Set-Cookie"]]
+ end
+
+ self["Set-Cookie"].reject! { |cookie|
+ cookie =~ /\A#{Utils.escape(key)}=/
+ }
+
+ set_cookie(key,
+ {:value => '', :path => nil, :domain => nil,
+ :expires => Time.at(0) }.merge(value))
+ end
+
+
+ def finish(&block)
+ block.call if block
+ [status.to_i, header.to_hash, self]
+ end
+ alias to_a finish # For *response
+
+ def each(&block)
+ @writer = block
+ @body.each(&block)
+ end
+
+ def write(str)
+ @writer.call str
+ end
+ end
+end
View
68 lib/rack/utils.rb
@@ -0,0 +1,68 @@
+module Rack
+ module Utils
+ # Performs URI escaping so that you can construct proper
+ # query strings faster. Use this rather than the cgi.rb
+ # version since it's faster. (Stolen from Camping).
+ def escape(s)
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
+ }.tr(' ', '+')
+ end
+ module_function :escape
+
+ # Unescapes a URI escaped string. (Stolen from Camping).
+ def unescape(s)
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
+ [$1.delete('%')].pack('H*')
+ }
+ end
+ module_function :unescape
+
+ # Stolen from Mongrel:
+ # Parses a query string by breaking it up at the '&'
+ # and ';' characters. You can also use this to parse
+ # cookies by changing the characters used in the second
+ # parameter (which defaults to '&;'.
+
+ def parse_query(qs, d = '&;')
+ params = {}
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
+ k, v=unescape(p).split('=',2)
+ if cur = params[k]
+ if cur.class == Array
+ params[k] << v
+ else
+ params[k] = [cur, v]
+ end
+ else
+ params[k] = v
+ end
+ }
+
+ return params
+ end
+ module_function :parse_query
+
+ class HeaderHash < Hash
+ def initialize(hash={})
+ hash.each { |k, v| self[k] = v }
+ end
+
+ def to_hash
+ {}.replace(self)
+ end
+
+ def [](k)
+ super capitalize(k)
+ end
+
+ def []=(k, v)
+ super capitalize(k), v
+ end
+
+ def capitalize(k)
+ k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
+ end
+ end
+ end
+end
View
65 test/spec_rack_response.rb
@@ -0,0 +1,65 @@
+require 'test/spec'
+
+require 'rack/response'
+
+context "Rack::Response" do
+ specify "has sensible default values" do
+ response = Rack::Response.new
+ status, header, body = response.finish
+ status.should.equal 200
+ header.should.equal "Content-Type" => "text/html"
+ body.each { |part|
+ part.should.equal ""
+ }
+
+ response = Rack::Response.new
+ status, header, body = *response
+ status.should.equal 200
+ header.should.equal "Content-Type" => "text/html"
+ body.each { |part|
+ part.should.equal ""
+ }
+ end
+
+ specify "can be written to" do
+ response = Rack::Response.new
+
+ status, header, body = response.finish do
+ response.write "foo"
+ response.write "bar"
+ response.write "baz"
+ end
+
+ parts = []
+ body.each { |part| parts << part }
+
+ parts.should.equal ["foo", "bar", "baz"]
+ end
+
+ specify "can set and read headers" do
+ response = Rack::Response.new
+ response["Content-Type"].should.equal "text/html"
+ response["Content-Type"] = "text/plain"
+ response["Content-Type"].should.equal "text/plain"
+ end
+
+ specify "can set cookies" do
+ response = Rack::Response.new
+
+ response.set_cookie "foo", "bar"
+ response["Set-Cookie"].should.equal "foo=bar"
+ response.set_cookie "foo2", "bar2"
+ response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"]
+ response.set_cookie "foo3", "bar3"
+ response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
+ end
+
+ specify "can delete cookies" do
+ response = Rack::Response.new
+ response.set_cookie "foo", "bar"
+ response.set_cookie "foo2", "bar2"
+ response.delete_cookie "foo"
+ response["Set-Cookie"].should.equal ["foo2=bar2",
+ "foo=; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
+ end
+end
View
32 test/spec_rack_utils.rb
@@ -0,0 +1,32 @@
+require 'rack/utils'
+
+context "Rack::Utils::HeaderHash" do
+ specify "should capitalize on all accesses" do
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
+ h["foo"].should.equal "bar"
+ h["Foo"].should.equal "bar"
+ h["FOO"].should.equal "bar"
+
+ h.to_hash.should.equal "Foo" => "bar"
+
+ h["bar-zzle"] = "quux"
+
+ h.to_hash.should.equal "Foo" => "bar", "Bar-Zzle" => "quux"
+ end
+
+ specify "should capitalize correctly" do
+ h = Rack::Utils::HeaderHash.new
+
+ h.capitalize("foo").should.equal "Foo"
+ h.capitalize("foo-bar").should.equal "Foo-Bar"
+ h.capitalize("foo_bar").should.equal "Foo_Bar"
+ h.capitalize("foo bar").should.equal "Foo Bar"
+ h.capitalize("foo-bar-quux").should.equal "Foo-Bar-Quux"
+ h.capitalize("foo-bar-2quux").should.equal "Foo-Bar-2quux"
+ end
+
+ specify "should be converted to real Hash" do
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
+ h.to_hash.should.be.instance_of Hash
+ end
+end

0 comments on commit 7ed819a

Please sign in to comment.