technomancy-rack 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/KNOWN-ISSUES +18 -0
  2. data/README +242 -0
  3. data/bin/rackup +183 -0
  4. data/lib/rack.rb +92 -0
  5. data/lib/rack/adapter/camping.rb +22 -0
  6. data/lib/rack/auth/abstract/handler.rb +28 -0
  7. data/lib/rack/auth/abstract/request.rb +37 -0
  8. data/lib/rack/auth/basic.rb +58 -0
  9. data/lib/rack/auth/digest/md5.rb +124 -0
  10. data/lib/rack/auth/digest/nonce.rb +51 -0
  11. data/lib/rack/auth/digest/params.rb +55 -0
  12. data/lib/rack/auth/digest/request.rb +40 -0
  13. data/lib/rack/auth/openid.rb +116 -0
  14. data/lib/rack/builder.rb +56 -0
  15. data/lib/rack/cascade.rb +36 -0
  16. data/lib/rack/commonlogger.rb +56 -0
  17. data/lib/rack/file.rb +112 -0
  18. data/lib/rack/handler/cgi.rb +57 -0
  19. data/lib/rack/handler/fastcgi.rb +83 -0
  20. data/lib/rack/handler/lsws.rb +52 -0
  21. data/lib/rack/handler/mongrel.rb +97 -0
  22. data/lib/rack/handler/scgi.rb +57 -0
  23. data/lib/rack/handler/webrick.rb +57 -0
  24. data/lib/rack/lint.rb +394 -0
  25. data/lib/rack/lobster.rb +65 -0
  26. data/lib/rack/mock.rb +172 -0
  27. data/lib/rack/recursive.rb +57 -0
  28. data/lib/rack/reloader.rb +64 -0
  29. data/lib/rack/request.rb +197 -0
  30. data/lib/rack/response.rb +166 -0
  31. data/lib/rack/session/abstract/id.rb +126 -0
  32. data/lib/rack/session/cookie.rb +71 -0
  33. data/lib/rack/session/memcache.rb +83 -0
  34. data/lib/rack/session/pool.rb +67 -0
  35. data/lib/rack/showexceptions.rb +344 -0
  36. data/lib/rack/showstatus.rb +103 -0
  37. data/lib/rack/static.rb +38 -0
  38. data/lib/rack/urlmap.rb +48 -0
  39. data/lib/rack/utils.rb +256 -0
  40. data/test/spec_rack_auth_basic.rb +69 -0
  41. data/test/spec_rack_auth_digest.rb +169 -0
  42. data/test/spec_rack_builder.rb +50 -0
  43. data/test/spec_rack_camping.rb +47 -0
  44. data/test/spec_rack_cascade.rb +50 -0
  45. data/test/spec_rack_cgi.rb +91 -0
  46. data/test/spec_rack_commonlogger.rb +32 -0
  47. data/test/spec_rack_fastcgi.rb +91 -0
  48. data/test/spec_rack_file.rb +40 -0
  49. data/test/spec_rack_lint.rb +317 -0
  50. data/test/spec_rack_lobster.rb +45 -0
  51. data/test/spec_rack_mock.rb +152 -0
  52. data/test/spec_rack_mongrel.rb +165 -0
  53. data/test/spec_rack_recursive.rb +77 -0
  54. data/test/spec_rack_request.rb +384 -0
  55. data/test/spec_rack_response.rb +167 -0
  56. data/test/spec_rack_session_cookie.rb +49 -0
  57. data/test/spec_rack_session_memcache.rb +100 -0
  58. data/test/spec_rack_session_pool.rb +84 -0
  59. data/test/spec_rack_showexceptions.rb +21 -0
  60. data/test/spec_rack_showstatus.rb +71 -0
  61. data/test/spec_rack_static.rb +37 -0
  62. data/test/spec_rack_urlmap.rb +175 -0
  63. data/test/spec_rack_utils.rb +69 -0
  64. data/test/spec_rack_webrick.rb +106 -0
  65. data/test/testrequest.rb +43 -0
  66. metadata +167 -0
@@ -0,0 +1,56 @@
1
+ module Rack
2
+ # Rack::Builder implements a small DSL to iteratively construct Rack
3
+ # applications.
4
+ #
5
+ # Example:
6
+ #
7
+ # app = Rack::Builder.new {
8
+ # use Rack::CommonLogger
9
+ # use Rack::ShowExceptions
10
+ # map "/lobster" do
11
+ # use Rack::Lint
12
+ # run Rack::Lobster.new
13
+ # end
14
+ # }
15
+ #
16
+ # +use+ adds a middleware to the stack, +run+ dispatches to an application.
17
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
18
+
19
+ class Builder
20
+ def initialize(&block)
21
+ @ins = []
22
+ instance_eval(&block)
23
+ end
24
+
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
31
+ end
32
+
33
+ def run(app)
34
+ @ins << app #lambda { |nothing| app }
35
+ end
36
+
37
+ def map(path, &block)
38
+ if @ins.last.kind_of? Hash
39
+ @ins.last[path] = Rack::Builder.new(&block).to_app
40
+ else
41
+ @ins << {}
42
+ map(path, &block)
43
+ end
44
+ end
45
+
46
+ def to_app
47
+ @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
48
+ inner_app = @ins.last
49
+ @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
50
+ end
51
+
52
+ def call(env)
53
+ to_app.call(env)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,36 @@
1
+ module Rack
2
+ # Rack::Cascade tries an request on several apps, and returns the
3
+ # first response that is not 404 (or in a list of configurable
4
+ # status codes).
5
+
6
+ class Cascade
7
+ attr_reader :apps
8
+
9
+ def initialize(apps, catch=404)
10
+ @apps = apps
11
+ @catch = [*catch]
12
+ end
13
+
14
+ def call(env)
15
+ status = headers = body = nil
16
+ raise ArgumentError, "empty cascade" if @apps.empty?
17
+ @apps.each { |app|
18
+ begin
19
+ status, headers, body = app.call(env)
20
+ break unless @catch.include?(status.to_i)
21
+ end
22
+ }
23
+ [status, headers, body]
24
+ end
25
+
26
+ def add app
27
+ @apps << app
28
+ end
29
+
30
+ def include? app
31
+ @apps.include? app
32
+ end
33
+
34
+ alias_method :<<, :add
35
+ end
36
+ end
@@ -0,0 +1,56 @@
1
+ module Rack
2
+ # Rack::CommonLogger forwards every request to an +app+ given, and
3
+ # logs a line in the Apache common log format to the +logger+, or
4
+ # rack.errors by default.
5
+
6
+ class CommonLogger
7
+ def initialize(app, logger=nil)
8
+ @app = app
9
+ @logger = logger
10
+ end
11
+
12
+ def call(env)
13
+ dup._call(env)
14
+ end
15
+
16
+ def _call(env)
17
+ @env = env
18
+ @logger ||= self
19
+ @time = Time.now
20
+ @status, @header, @body = @app.call(env)
21
+ [@status, @header, self]
22
+ end
23
+
24
+ # By default, log to rack.errors.
25
+ def <<(str)
26
+ @env["rack.errors"].write(str)
27
+ @env["rack.errors"].flush
28
+ end
29
+
30
+ def each
31
+ length = 0
32
+ @body.each { |part|
33
+ length += part.size
34
+ yield part
35
+ }
36
+
37
+ @now = Time.now
38
+
39
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
40
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
41
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
42
+ @logger << %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
43
+ [@env["REMOTE_ADDR"] || "-",
44
+ @env["REMOTE_USER"] || "-",
45
+ @now.strftime("%d/%b/%Y %H:%M:%S"),
46
+ @env["REQUEST_METHOD"],
47
+ @env["PATH_INFO"],
48
+ @env["QUERY_STRING"].empty? ? "" : "?"+@env["QUERY_STRING"],
49
+ @env["HTTP_VERSION"],
50
+ @status.to_s[0..3],
51
+ (length.zero? ? "-" : length.to_s),
52
+ @now - @time
53
+ ]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,112 @@
1
+ require 'time'
2
+
3
+ module Rack
4
+ # Rack::File serves files below the +root+ given, according to the
5
+ # path info of the Rack request.
6
+ #
7
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
8
+ # like sendfile on the +path+.
9
+
10
+ class File
11
+ attr_accessor :root
12
+ attr_accessor :path
13
+
14
+ def initialize(root)
15
+ @root = root
16
+ end
17
+
18
+ def call(env)
19
+ dup._call(env)
20
+ end
21
+
22
+ F = ::File
23
+
24
+ def _call(env)
25
+ if env["PATH_INFO"].include? ".."
26
+ return [403, {"Content-Type" => "text/plain"}, ["Forbidden\n"]]
27
+ end
28
+
29
+ @path = F.join(@root, Utils.unescape(env["PATH_INFO"]))
30
+ ext = F.extname(@path)[1..-1]
31
+
32
+ if F.file?(@path) && F.readable?(@path)
33
+ [200, {
34
+ "Last-Modified" => F.mtime(@path).rfc822,
35
+ "Content-Type" => MIME_TYPES[ext] || "text/plain",
36
+ "Content-Length" => F.size(@path).to_s
37
+ }, self]
38
+ else
39
+ return [404, {"Content-Type" => "text/plain"},
40
+ ["File not found: #{env["PATH_INFO"]}\n"]]
41
+ end
42
+ end
43
+
44
+ def each
45
+ F.open(@path, "rb") { |file|
46
+ while part = file.read(8192)
47
+ yield part
48
+ end
49
+ }
50
+ end
51
+
52
+ # :stopdoc:
53
+ # From WEBrick.
54
+ MIME_TYPES = {
55
+ "ai" => "application/postscript",
56
+ "asc" => "text/plain",
57
+ "avi" => "video/x-msvideo",
58
+ "bin" => "application/octet-stream",
59
+ "bmp" => "image/bmp",
60
+ "class" => "application/octet-stream",
61
+ "cer" => "application/pkix-cert",
62
+ "crl" => "application/pkix-crl",
63
+ "crt" => "application/x-x509-ca-cert",
64
+ #"crl" => "application/x-pkcs7-crl",
65
+ "css" => "text/css",
66
+ "dms" => "application/octet-stream",
67
+ "doc" => "application/msword",
68
+ "dvi" => "application/x-dvi",
69
+ "eps" => "application/postscript",
70
+ "etx" => "text/x-setext",
71
+ "exe" => "application/octet-stream",
72
+ "gif" => "image/gif",
73
+ "htm" => "text/html",
74
+ "html" => "text/html",
75
+ "jpe" => "image/jpeg",
76
+ "jpeg" => "image/jpeg",
77
+ "jpg" => "image/jpeg",
78
+ "js" => "text/javascript",
79
+ "lha" => "application/octet-stream",
80
+ "lzh" => "application/octet-stream",
81
+ "mov" => "video/quicktime",
82
+ "mpe" => "video/mpeg",
83
+ "mpeg" => "video/mpeg",
84
+ "mpg" => "video/mpeg",
85
+ "pbm" => "image/x-portable-bitmap",
86
+ "pdf" => "application/pdf",
87
+ "pgm" => "image/x-portable-graymap",
88
+ "png" => "image/png",
89
+ "pnm" => "image/x-portable-anymap",
90
+ "ppm" => "image/x-portable-pixmap",
91
+ "ppt" => "application/vnd.ms-powerpoint",
92
+ "ps" => "application/postscript",
93
+ "qt" => "video/quicktime",
94
+ "ras" => "image/x-cmu-raster",
95
+ "rb" => "text/plain",
96
+ "rd" => "text/plain",
97
+ "rtf" => "application/rtf",
98
+ "sgm" => "text/sgml",
99
+ "sgml" => "text/sgml",
100
+ "tif" => "image/tiff",
101
+ "tiff" => "image/tiff",
102
+ "txt" => "text/plain",
103
+ "xbm" => "image/x-xbitmap",
104
+ "xls" => "application/vnd.ms-excel",
105
+ "xml" => "text/xml",
106
+ "xpm" => "image/x-xpixmap",
107
+ "xwd" => "image/x-xwindowdump",
108
+ "zip" => "application/zip",
109
+ }
110
+ # :startdoc:
111
+ end
112
+ end
@@ -0,0 +1,57 @@
1
+ module Rack
2
+ module Handler
3
+ class CGI
4
+ def self.run(app, options=nil)
5
+ serve app
6
+ end
7
+
8
+ def self.serve(app)
9
+ env = ENV.to_hash
10
+ env.delete "HTTP_CONTENT_LENGTH"
11
+
12
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
13
+
14
+ env.update({"rack.version" => [0,1],
15
+ "rack.input" => STDIN,
16
+ "rack.errors" => STDERR,
17
+
18
+ "rack.multithread" => false,
19
+ "rack.multiprocess" => true,
20
+ "rack.run_once" => true,
21
+
22
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
23
+ })
24
+
25
+ env["QUERY_STRING"] ||= ""
26
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
27
+ env["REQUEST_PATH"] ||= "/"
28
+
29
+ status, headers, body = app.call(env)
30
+ begin
31
+ send_headers status, headers
32
+ send_body body
33
+ ensure
34
+ body.close if body.respond_to? :close
35
+ end
36
+ end
37
+
38
+ def self.send_headers(status, headers)
39
+ STDOUT.print "Status: #{status}\r\n"
40
+ headers.each { |k, vs|
41
+ vs.each { |v|
42
+ STDOUT.print "#{k}: #{v}\r\n"
43
+ }
44
+ }
45
+ STDOUT.print "\r\n"
46
+ STDOUT.flush
47
+ end
48
+
49
+ def self.send_body(body)
50
+ body.each { |part|
51
+ STDOUT.print part
52
+ STDOUT.flush
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,83 @@
1
+ require 'fcgi'
2
+
3
+ module Rack
4
+ module Handler
5
+ class FastCGI
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))
9
+ FCGI.each { |request|
10
+ serve request, app
11
+ }
12
+ end
13
+
14
+ module ProperStream # :nodoc:
15
+ def each # This is missing by default.
16
+ while line = gets
17
+ yield line
18
+ end
19
+ end
20
+
21
+ def read(*args)
22
+ if args.empty?
23
+ super || "" # Empty string on EOF.
24
+ else
25
+ super
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.serve(request, app)
31
+ env = request.env
32
+ env.delete "HTTP_CONTENT_LENGTH"
33
+
34
+ request.in.extend ProperStream
35
+
36
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
37
+
38
+ env.update({"rack.version" => [0,1],
39
+ "rack.input" => request.in,
40
+ "rack.errors" => request.err,
41
+
42
+ "rack.multithread" => false,
43
+ "rack.multiprocess" => true,
44
+ "rack.run_once" => false,
45
+
46
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
47
+ })
48
+
49
+ env["QUERY_STRING"] ||= ""
50
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
51
+ env["REQUEST_PATH"] ||= "/"
52
+ env.delete "PATH_INFO" if env["PATH_INFO"] == ""
53
+
54
+ status, headers, body = app.call(env)
55
+ begin
56
+ send_headers request.out, status, headers
57
+ send_body request.out, body
58
+ ensure
59
+ body.close if body.respond_to? :close
60
+ request.finish
61
+ end
62
+ end
63
+
64
+ def self.send_headers(out, status, headers)
65
+ out.print "Status: #{status}\r\n"
66
+ headers.each { |k, vs|
67
+ vs.each { |v|
68
+ out.print "#{k}: #{v}\r\n"
69
+ }
70
+ }
71
+ out.print "\r\n"
72
+ out.flush
73
+ end
74
+
75
+ def self.send_body(out, body)
76
+ body.each { |part|
77
+ out.print part
78
+ out.flush
79
+ }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -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