rack 0.1.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.

Files changed (54) hide show
  1. data/AUTHORS +3 -0
  2. data/COPYING +18 -0
  3. data/KNOWN-ISSUES +18 -0
  4. data/RDOX +144 -0
  5. data/README +154 -0
  6. data/Rakefile +174 -0
  7. data/SPEC +132 -0
  8. data/bin/rackup +148 -0
  9. data/contrib/rack_logo.svg +111 -0
  10. data/example/lobster.ru +4 -0
  11. data/lib/rack.rb +67 -0
  12. data/lib/rack/adapter/camping.rb +16 -0
  13. data/lib/rack/adapter/rails.rb +65 -0
  14. data/lib/rack/builder.rb +52 -0
  15. data/lib/rack/cascade.rb +26 -0
  16. data/lib/rack/commonlogger.rb +56 -0
  17. data/lib/rack/file.rb +108 -0
  18. data/lib/rack/handler/cgi.rb +57 -0
  19. data/lib/rack/handler/fastcgi.rb +81 -0
  20. data/lib/rack/handler/mongrel.rb +57 -0
  21. data/lib/rack/handler/webrick.rb +56 -0
  22. data/lib/rack/lint.rb +394 -0
  23. data/lib/rack/lobster.rb +65 -0
  24. data/lib/rack/mock.rb +183 -0
  25. data/lib/rack/recursive.rb +57 -0
  26. data/lib/rack/reloader.rb +64 -0
  27. data/lib/rack/request.rb +112 -0
  28. data/lib/rack/response.rb +114 -0
  29. data/lib/rack/showexceptions.rb +344 -0
  30. data/lib/rack/urlmap.rb +50 -0
  31. data/lib/rack/utils.rb +176 -0
  32. data/test/cgi/lighttpd.conf +20 -0
  33. data/test/cgi/test +9 -0
  34. data/test/cgi/test.fcgi +9 -0
  35. data/test/cgi/test.ru +7 -0
  36. data/test/spec_rack_camping.rb +44 -0
  37. data/test/spec_rack_cascade.rb +35 -0
  38. data/test/spec_rack_cgi.rb +82 -0
  39. data/test/spec_rack_commonlogger.rb +32 -0
  40. data/test/spec_rack_fastcgi.rb +82 -0
  41. data/test/spec_rack_file.rb +32 -0
  42. data/test/spec_rack_lint.rb +317 -0
  43. data/test/spec_rack_lobster.rb +45 -0
  44. data/test/spec_rack_mock.rb +150 -0
  45. data/test/spec_rack_mongrel.rb +87 -0
  46. data/test/spec_rack_recursive.rb +77 -0
  47. data/test/spec_rack_request.rb +219 -0
  48. data/test/spec_rack_response.rb +110 -0
  49. data/test/spec_rack_showexceptions.rb +21 -0
  50. data/test/spec_rack_urlmap.rb +140 -0
  51. data/test/spec_rack_utils.rb +57 -0
  52. data/test/spec_rack_webrick.rb +89 -0
  53. data/test/testrequest.rb +43 -0
  54. metadata +117 -0
@@ -0,0 +1,50 @@
1
+ module Rack
2
+ # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
+ # dispatches accordingly. Support for HTTP/1.1 host names exists if
4
+ # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
5
+ #
6
+ # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
7
+ # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
8
+ # PATH_INFO. This should be taken care of when you need to
9
+ # reconstruct the URL in order to create links.
10
+ #
11
+ # URLMap dispatches in such a way that the longest paths are tried
12
+ # first, since they are most specific.
13
+
14
+ class URLMap
15
+ def initialize(map)
16
+ @mapping = map.map { |location, app|
17
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
18
+ host, location = $1, $2
19
+ else
20
+ host = nil
21
+ end
22
+
23
+ location = "" if location == "/"
24
+
25
+ [host, location, app]
26
+ }.sort_by { |(h, l, a)| -l.size } # Longest path first
27
+ end
28
+
29
+ def call(env)
30
+ path = env["PATH_INFO"].to_s.squeeze("/")
31
+ @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
40
+ env["PATH_INFO"] = path[location.size..-1]
41
+ env["PATH_INFO"].gsub!(/\/\z/, '')
42
+ env["PATH_INFO"] = "/" if env["PATH_INFO"].empty?
43
+ return app.call(env)
44
+ end
45
+ }
46
+ [404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]]
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,176 @@
1
+ require 'tempfile'
2
+
3
+ module Rack
4
+ # Rack::Utils contains a grab-bag of useful methods for writing web
5
+ # applications adopted from all kinds of Ruby libraries.
6
+
7
+ module Utils
8
+ # Performs URI escaping so that you can construct proper
9
+ # query strings faster. Use this rather than the cgi.rb
10
+ # version since it's faster. (Stolen from Camping).
11
+ def escape(s)
12
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
13
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
14
+ }.tr(' ', '+')
15
+ end
16
+ module_function :escape
17
+
18
+ # Unescapes a URI escaped string. (Stolen from Camping).
19
+ def unescape(s)
20
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
21
+ [$1.delete('%')].pack('H*')
22
+ }
23
+ end
24
+ module_function :unescape
25
+
26
+ # Stolen from Mongrel:
27
+ # Parses a query string by breaking it up at the '&'
28
+ # and ';' characters. You can also use this to parse
29
+ # cookies by changing the characters used in the second
30
+ # parameter (which defaults to '&;'.
31
+
32
+ def parse_query(qs, d = '&;')
33
+ params = {}
34
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
35
+ k, v=unescape(p).split('=',2)
36
+ if cur = params[k]
37
+ if cur.class == Array
38
+ params[k] << v
39
+ else
40
+ params[k] = [cur, v]
41
+ end
42
+ else
43
+ params[k] = v
44
+ end
45
+ }
46
+
47
+ return params
48
+ end
49
+ module_function :parse_query
50
+
51
+ # Escape ampersands, brackets and quotes to their HTML/XML entities.
52
+ def escape_html(string)
53
+ string.to_s.gsub("&", "&amp;").
54
+ gsub("<", "&lt;").
55
+ gsub(">", "&gt;").
56
+ gsub("'", "&#39;").
57
+ gsub('"', "&quot;")
58
+ end
59
+ module_function :escape_html
60
+
61
+ # A case-normalizing Hash, adjusting on [] and []=.
62
+ class HeaderHash < Hash
63
+ def initialize(hash={})
64
+ hash.each { |k, v| self[k] = v }
65
+ end
66
+
67
+ def to_hash
68
+ {}.replace(self)
69
+ end
70
+
71
+ def [](k)
72
+ super capitalize(k)
73
+ end
74
+
75
+ def []=(k, v)
76
+ super capitalize(k), v
77
+ end
78
+
79
+ def capitalize(k)
80
+ k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
81
+ end
82
+ end
83
+
84
+ # A multipart form data parser, adapted from IOWA.
85
+ #
86
+ # Usually, Rack::Request#POST takes care of calling this.
87
+
88
+ module Multipart
89
+ EOL = "\r\n"
90
+
91
+ def self.parse_multipart(env)
92
+ unless env['CONTENT_TYPE'] =~
93
+ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
94
+ nil
95
+ else
96
+ boundary = "--#{$1}"
97
+
98
+ params = {}
99
+ buf = ""
100
+ content_length = env['CONTENT_LENGTH'].to_i
101
+ input = env['rack.input']
102
+
103
+ boundary_size = boundary.size + EOL.size
104
+ bufsize = 16384
105
+
106
+ content_length -= boundary_size
107
+
108
+ status = input.read(boundary_size)
109
+ raise EOFError, "bad content body" unless status == boundary + EOL
110
+
111
+ rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/
112
+
113
+ loop {
114
+ head = nil
115
+ body = ''
116
+ filename = content_type = name = nil
117
+
118
+ until head && buf =~ rx
119
+ if !head && i = buf.index("\r\n\r\n")
120
+ head = buf.slice!(0, i+2) # First \r\n
121
+ buf.slice!(0, 2) # Second \r\n
122
+
123
+ filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
124
+ content_type = head[/Content-Type: (.*)\r\n/ni, 1]
125
+ name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
126
+
127
+ body = Tempfile.new("RackMultipart") if filename
128
+
129
+ next
130
+ end
131
+
132
+ # Save the read body part.
133
+ if head && (boundary_size+4 < buf.size)
134
+ body << buf.slice!(0, buf.size - (boundary_size+4))
135
+ end
136
+
137
+ c = input.read(bufsize < content_length ? bufsize : content_length)
138
+ raise EOFError, "bad content body" if c.nil? || c.empty?
139
+ buf << c
140
+ content_length -= c.size
141
+ end
142
+
143
+ # Save the rest.
144
+ if i = buf.index(rx)
145
+ body << buf.slice!(0, i)
146
+ buf.slice!(0, boundary_size+2)
147
+
148
+ content_length = -1 if $1 == "--"
149
+ end
150
+
151
+ if filename
152
+ body.rewind
153
+ data = {:filename => filename, :type => content_type,
154
+ :name => name, :tempfile => body, :head => head}
155
+ else
156
+ data = body
157
+ end
158
+
159
+ if name
160
+ if name =~ /\[\]\z/
161
+ params[name] ||= []
162
+ params[name] << data
163
+ else
164
+ params[name] = data
165
+ end
166
+ end
167
+
168
+ break if buf.empty? || content_length == -1
169
+ }
170
+
171
+ params
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,20 @@
1
+ server.modules = ("mod_fastcgi", "mod_cgi")
2
+ server.document-root = "."
3
+ server.errorlog = "lighttpd.errors"
4
+ server.port = 9203
5
+
6
+ server.event-handler = "freebsd-kqueue"
7
+
8
+ cgi.assign = ("/test" => "/usr/local/bin/ruby",
9
+ # ".ru" => ""
10
+ )
11
+
12
+ fastcgi.server = ("test.fcgi" => ("localhost" =>
13
+ ("min-procs" => 1,
14
+ "socket" => "/tmp/rack-test-fcgi",
15
+ "bin-path" => "test.fcgi")),
16
+ "test.ru" => ("localhost" =>
17
+ ("min-procs" => 1,
18
+ "socket" => "/tmp/rack-test-ru-fcgi",
19
+ "bin-path" => "test.ru")),
20
+ )
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- ruby -*-
3
+
4
+ $: << File.join(File.dirname(__FILE__), "..", "..", "lib")
5
+
6
+ require 'rack'
7
+ require '../testrequest'
8
+
9
+ Rack::Handler::CGI.run(Rack::Lint.new(TestRequest.new))
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- ruby -*-
3
+
4
+ $: << File.join(File.dirname(__FILE__), "..", "..", "lib")
5
+
6
+ require 'rack'
7
+ require '../testrequest'
8
+
9
+ Rack::Handler::FastCGI.run(Rack::Lint.new(TestRequest.new))
@@ -0,0 +1,7 @@
1
+ #!/usr/local/bin/ruby ../../bin/rackup
2
+ #\ -E deployment -I ~/projects/rack/lib
3
+ # -*- ruby -*-
4
+
5
+ require '../testrequest'
6
+
7
+ run TestRequest.new
@@ -0,0 +1,44 @@
1
+ require 'test/spec'
2
+ require 'stringio'
3
+
4
+ require 'rack/mock'
5
+
6
+ $-w, w = nil, $-w # yuck
7
+ require 'camping'
8
+ require 'rack/adapter/camping'
9
+
10
+ Camping.goes :CampApp
11
+ module CampApp
12
+ module Controllers
13
+ class HW < R('/')
14
+ def get
15
+ "Camping works!"
16
+ end
17
+
18
+ def post
19
+ "Data: #{input.foo}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ $-w = w
25
+
26
+ context "Rack::Adapter::Camping" do
27
+ specify "works with GET" do
28
+ res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
29
+ get("/")
30
+
31
+ res.should.be.ok
32
+ res["Content-Type"].should.equal "text/html"
33
+
34
+ res.body.should.equal "Camping works!"
35
+ end
36
+
37
+ specify "works with POST" do
38
+ res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
39
+ post("/", :input => "foo=bar")
40
+
41
+ res.should.be.ok
42
+ res.body.should.equal "Data: bar"
43
+ end
44
+ end
@@ -0,0 +1,35 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/cascade'
4
+ require 'rack/mock'
5
+
6
+ require 'rack/urlmap'
7
+ require 'rack/file'
8
+
9
+ context "Rack::Cascade" do
10
+ docroot = File.expand_path(File.dirname(__FILE__))
11
+ app1 = Rack::File.new(docroot)
12
+
13
+ app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" })
14
+
15
+ app3 = Rack::URLMap.new("/foo" => lambda { |env|
16
+ [200, { "Content-Type" => "text/plain"}, [""]]})
17
+
18
+ specify "should dispatch onward on 404 by default" do
19
+ cascade = Rack::Cascade.new([app1, app2, app3])
20
+ Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
21
+ Rack::MockRequest.new(cascade).get("/foo").should.be.ok
22
+ Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
23
+ Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
24
+ end
25
+
26
+ specify "should dispatch onward on whatever is passed" do
27
+ cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
28
+ Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
29
+ end
30
+
31
+ specify "should fail if empty" do
32
+ lambda { Rack::MockRequest.new(Rack::Cascade.new([])).get("/") }.
33
+ should.raise(ArgumentError)
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ require 'test/spec'
2
+ require 'testrequest'
3
+
4
+ pid = fork {
5
+ exec "cd #{File.join(File.dirname(__FILE__), 'cgi')} && lighttpd -D -f lighttpd.conf"
6
+ }
7
+
8
+ at_exit {
9
+ Process.kill 15, pid
10
+ }
11
+
12
+ context "Rack::Handler::CGI" do
13
+ include TestRequest::Helpers
14
+
15
+ setup do
16
+ @host = '0.0.0.0'
17
+ @port = 9203
18
+ end
19
+
20
+ specify "should respond" do
21
+ lambda {
22
+ GET("/test")
23
+ }.should.not.raise
24
+ end
25
+
26
+ specify "should be a lighttpd" do
27
+ GET("/test")
28
+ status.should.be 200
29
+ response["SERVER_SOFTWARE"].should =~ /lighttpd/
30
+ response["HTTP_VERSION"].should.equal "HTTP/1.1"
31
+ response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
32
+ response["SERVER_PORT"].should.equal "9203"
33
+ response["SERVER_NAME"].should =~ "0.0.0.0"
34
+ end
35
+
36
+ specify "should have rack headers" do
37
+ GET("/test")
38
+ response["rack.version"].should.equal [0,1]
39
+ response["rack.multithread"].should.be false
40
+ response["rack.multiprocess"].should.be true
41
+ response["rack.run_once"].should.be true
42
+ end
43
+
44
+ specify "should have CGI headers on GET" do
45
+ GET("/test")
46
+ response["REQUEST_METHOD"].should.equal "GET"
47
+ response["SCRIPT_NAME"].should.equal "/test"
48
+ response["REQUEST_PATH"].should.equal "/"
49
+ response["PATH_INFO"].should.be.nil
50
+ response["QUERY_STRING"].should.equal ""
51
+ response["test.postdata"].should.equal ""
52
+
53
+ GET("/test/foo?quux=1")
54
+ response["REQUEST_METHOD"].should.equal "GET"
55
+ response["SCRIPT_NAME"].should.equal "/test"
56
+ response["REQUEST_PATH"].should.equal "/"
57
+ response["PATH_INFO"].should.equal "/foo"
58
+ response["QUERY_STRING"].should.equal "quux=1"
59
+ end
60
+
61
+ specify "should have CGI headers on POST" do
62
+ POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
63
+ status.should.equal 200
64
+ response["REQUEST_METHOD"].should.equal "POST"
65
+ response["SCRIPT_NAME"].should.equal "/test"
66
+ response["REQUEST_PATH"].should.equal "/"
67
+ response["QUERY_STRING"].should.equal ""
68
+ response["HTTP_X_TEST_HEADER"].should.equal "42"
69
+ response["test.postdata"].should.equal "rack-form-data=23"
70
+ end
71
+
72
+ specify "should support HTTP auth" do
73
+ GET("/test", {:user => "ruth", :passwd => "secret"})
74
+ response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
75
+ end
76
+
77
+ specify "should set status" do
78
+ GET("/test?secret")
79
+ status.should.equal 403
80
+ response["rack.url_scheme"].should.equal "http"
81
+ end
82
+ end
@@ -0,0 +1,32 @@
1
+ require 'test/spec'
2
+ require 'stringio'
3
+
4
+ require 'rack/commonlogger'
5
+ require 'rack/lobster'
6
+ require 'rack/mock'
7
+
8
+ context "Rack::CommonLogger" do
9
+ app = lambda { |env|
10
+ [200,
11
+ {"Content-Type" => "text/html"},
12
+ ["foo"]]}
13
+
14
+ specify "should log to rack.errors by default" do
15
+ log = StringIO.new
16
+ res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
17
+
18
+ res.errors.should.not.be.empty
19
+ res.errors.should =~ /GET /
20
+ res.errors.should =~ / 200 / # status
21
+ res.errors.should =~ / 3 / # length
22
+ end
23
+
24
+ specify "should log to anything with <<" do
25
+ log = ""
26
+ res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
27
+
28
+ log.should =~ /GET /
29
+ log.should =~ / 200 / # status
30
+ log.should =~ / 3 / # length
31
+ end
32
+ end