builder.rb 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. module Rack
  2. # Rack::Builder implements a small DSL to iteratively construct Rack
  3. # applications.
  4. #
  5. # Example:
  6. #
  7. # require 'rack/lobster'
  8. # app = Rack::Builder.new do
  9. # use Rack::CommonLogger
  10. # use Rack::ShowExceptions
  11. # map "/lobster" do
  12. # use Rack::Lint
  13. # run Rack::Lobster.new
  14. # end
  15. # end
  16. #
  17. # run app
  18. #
  19. # Or
  20. #
  21. # app = Rack::Builder.app do
  22. # use Rack::CommonLogger
  23. # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
  24. # end
  25. #
  26. # run app
  27. #
  28. # +use+ adds a middleware to the stack, +run+ dispatches to an application.
  29. # You can use +map+ to construct a Rack::URLMap in a convenient way.
  30. class Builder
  31. def self.parse_file(config, opts = Server::Options.new)
  32. options = {}
  33. if config =~ /\.ru$/
  34. cfgfile = ::File.read(config)
  35. if cfgfile[/^#\\(.*)/] && opts
  36. options = opts.parse! $1.split(/\s+/)
  37. end
  38. cfgfile.sub!(/^__END__\n.*\Z/m, '')
  39. app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
  40. TOPLEVEL_BINDING, config
  41. else
  42. require config
  43. app = Object.const_get(::File.basename(config, '.rb').capitalize)
  44. end
  45. return app, options
  46. end
  47. def initialize(default_app = nil,&block)
  48. @use, @map, @run = [], nil, default_app
  49. instance_eval(&block) if block_given?
  50. end
  51. def self.app(default_app = nil, &block)
  52. self.new(default_app, &block).to_app
  53. end
  54. # Specifies a middleware to use in a stack.
  55. #
  56. # class Middleware
  57. # def initialize(app)
  58. # @app = app
  59. # end
  60. #
  61. # def call(env)
  62. # env["rack.some_header"] = "setting an example"
  63. # @app.call(env)
  64. # end
  65. # end
  66. #
  67. # use Middleware
  68. # run lambda { |env| [200, { "Content-Type => "text/plain" }, ["OK"]] }
  69. #
  70. # All requests through to this application will first be processed by the middleware class.
  71. # The +call+ method in this example sets an additional environment key which then can be
  72. # referenced in the application if required.
  73. def use(middleware, *args, &block)
  74. if @map
  75. mapping, @map = @map, nil
  76. @use << proc { |app| generate_map app, mapping }
  77. end
  78. @use << proc { |app| middleware.new(app, *args, &block) }
  79. end
  80. # Takes an argument that is an object that responds to #call and returns a Rack response.
  81. # The simplest form of this is a lambda object:
  82. #
  83. # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
  84. #
  85. # However this could also be a class:
  86. #
  87. # class Heartbeat
  88. # def self.call(env)
  89. # [200, { "Content-Type" => "text/plain" }, ["OK"]]
  90. # end
  91. # end
  92. #
  93. # run Heartbeat
  94. def run(app)
  95. @run = app
  96. end
  97. # Creates a route within the application.
  98. #
  99. # Rack::Builder.app do
  100. # map '/' do
  101. # run Heartbeat
  102. # end
  103. # end
  104. #
  105. # The +use+ method can also be used here to specify middleware to run under a specific path:
  106. #
  107. # Rack::Builder.app do
  108. # map '/' do
  109. # use Middleware
  110. # run Heartbeat
  111. # end
  112. # end
  113. #
  114. # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
  115. #
  116. def map(path, &block)
  117. @map ||= {}
  118. @map[path] = block
  119. end
  120. def to_app
  121. app = @map ? generate_map(@run, @map) : @run
  122. fail "missing run or map statement" unless app
  123. @use.reverse.inject(app) { |a,e| e[a] }
  124. end
  125. def call(env)
  126. to_app.call(env)
  127. end
  128. private
  129. def generate_map(default_app, mapping)
  130. mapped = default_app ? {'/' => default_app} : {}
  131. mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
  132. URLMap.new(mapped)
  133. end
  134. end
  135. end