roda-cj 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +13 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +715 -0
  5. data/Rakefile +124 -0
  6. data/lib/roda/plugins/all_verbs.rb +48 -0
  7. data/lib/roda/plugins/default_headers.rb +50 -0
  8. data/lib/roda/plugins/error_handler.rb +69 -0
  9. data/lib/roda/plugins/flash.rb +108 -0
  10. data/lib/roda/plugins/h.rb +24 -0
  11. data/lib/roda/plugins/halt.rb +79 -0
  12. data/lib/roda/plugins/header_matchers.rb +57 -0
  13. data/lib/roda/plugins/hooks.rb +106 -0
  14. data/lib/roda/plugins/indifferent_params.rb +47 -0
  15. data/lib/roda/plugins/middleware.rb +88 -0
  16. data/lib/roda/plugins/multi_route.rb +77 -0
  17. data/lib/roda/plugins/not_found.rb +62 -0
  18. data/lib/roda/plugins/pass.rb +34 -0
  19. data/lib/roda/plugins/render.rb +217 -0
  20. data/lib/roda/plugins/streaming.rb +165 -0
  21. data/lib/roda/version.rb +3 -0
  22. data/lib/roda.rb +610 -0
  23. data/spec/composition_spec.rb +19 -0
  24. data/spec/env_spec.rb +11 -0
  25. data/spec/integration_spec.rb +63 -0
  26. data/spec/matchers_spec.rb +683 -0
  27. data/spec/module_spec.rb +29 -0
  28. data/spec/opts_spec.rb +42 -0
  29. data/spec/plugin/all_verbs_spec.rb +29 -0
  30. data/spec/plugin/default_headers_spec.rb +63 -0
  31. data/spec/plugin/error_handler_spec.rb +67 -0
  32. data/spec/plugin/flash_spec.rb +123 -0
  33. data/spec/plugin/h_spec.rb +13 -0
  34. data/spec/plugin/halt_spec.rb +62 -0
  35. data/spec/plugin/header_matchers_spec.rb +61 -0
  36. data/spec/plugin/hooks_spec.rb +97 -0
  37. data/spec/plugin/indifferent_params_spec.rb +13 -0
  38. data/spec/plugin/middleware_spec.rb +52 -0
  39. data/spec/plugin/multi_route_spec.rb +98 -0
  40. data/spec/plugin/not_found_spec.rb +99 -0
  41. data/spec/plugin/pass_spec.rb +23 -0
  42. data/spec/plugin/render_spec.rb +148 -0
  43. data/spec/plugin/streaming_spec.rb +52 -0
  44. data/spec/plugin_spec.rb +61 -0
  45. data/spec/redirect_spec.rb +24 -0
  46. data/spec/request_spec.rb +55 -0
  47. data/spec/response_spec.rb +131 -0
  48. data/spec/session_spec.rb +35 -0
  49. data/spec/spec_helper.rb +89 -0
  50. data/spec/version_spec.rb +8 -0
  51. metadata +136 -0
@@ -0,0 +1,47 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The indifferent_params plugin adds a +params+ instance
4
+ # method which returns a copy of the request params hash
5
+ # that will automatically convert symbols to strings.
6
+ # Example:
7
+ #
8
+ # plugin :indifferent_params
9
+ #
10
+ # route do |r|
11
+ # params[:foo]
12
+ # end
13
+ #
14
+ # The params hash is initialized lazily, so you only pay
15
+ # the penalty of copying the request params if you call
16
+ # the +params+ method.
17
+ module IndifferentParams
18
+ module InstanceMethods
19
+ # A copy of the request params that will automatically
20
+ # convert symbols to strings.
21
+ def params
22
+ @_params ||= indifferent_params(request.params)
23
+ end
24
+
25
+ private
26
+
27
+ # Recursively process the request params and convert
28
+ # hashes to support indifferent access, leaving
29
+ # other values alone.
30
+ def indifferent_params(params)
31
+ case params
32
+ when Hash
33
+ h = Hash.new{|h, k| h[k.to_s] if Symbol === k}
34
+ params.each{|k, v| h[k] = indifferent_params(v)}
35
+ h
36
+ when Array
37
+ params.map{|x| indifferent_params(x)}
38
+ else
39
+ params
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ register_plugin(:indifferent_params, IndifferentParams)
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The middleware plugin allows the Roda app to be used as
4
+ # rack middleware.
5
+ #
6
+ # In the example below, requests to /mid will return Mid
7
+ # by the Mid middleware, and requests to /app will not be
8
+ # matched by the Mid middleware, so they will be forwarded
9
+ # to App.
10
+ #
11
+ # class Mid < Roda
12
+ # plugin :middleware
13
+ #
14
+ # route do |r|
15
+ # r.is "mid" do
16
+ # "Mid"
17
+ # end
18
+ # end
19
+ # end
20
+ #
21
+ # class App < Roda
22
+ # use Mid
23
+ #
24
+ # route do |r|
25
+ # r.is "app" do
26
+ # "App"
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # run App
32
+ #
33
+ # Note that once you use the middleware plugin, you can only use the
34
+ # Roda app as middleware, and you will get errors if you attempt to
35
+ # use it as a regular app.
36
+ module Middleware
37
+ # Forward instances are what is actually used as middleware.
38
+ class Forwarder
39
+ # Store the current middleware and the next middleware to call.
40
+ def initialize(mid, app)
41
+ @mid = mid.app
42
+ @app = app
43
+ end
44
+
45
+ # When calling the middleware, first call the current middleware.
46
+ # If this returns a result, return that result directly. Otherwise,
47
+ # pass handling of the request to the next middleware.
48
+ def call(env)
49
+ res = nil
50
+
51
+ call_next = catch(:next) do
52
+ res = @mid.call(env)
53
+ false
54
+ end
55
+
56
+ if call_next
57
+ @app.call(env)
58
+ else
59
+ res
60
+ end
61
+ end
62
+ end
63
+
64
+ module ClassMethods
65
+ # If an argument is given, this is a middleware app, so create a Forwarder.
66
+ # Otherwise, this is a usual instance creation, so call super.
67
+ def new(app=nil)
68
+ if app
69
+ Forwarder.new(self, app)
70
+ else
71
+ super()
72
+ end
73
+ end
74
+
75
+ # Override the route block so that if no route matches, we throw so
76
+ # that the next middleware is called.
77
+ def route(&block)
78
+ super do |r|
79
+ instance_exec(r, &block)
80
+ throw :next, true
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ register_plugin(:middleware, Middleware)
87
+ end
88
+ end
@@ -0,0 +1,77 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The multi_route plugin allows for multiple named routes, which the
4
+ # main route block can dispatch to by name at any point. If the named
5
+ # route doesn't handle the request, execution will continue, and if the
6
+ # named route does handle the request, the response by the named route
7
+ # will be returned.
8
+ #
9
+ # Example:
10
+ #
11
+ # plugin :multi_route
12
+ #
13
+ # route(:foo) do |r|
14
+ # r.is 'bar' do
15
+ # '/foo/bar'
16
+ # end
17
+ # end
18
+ #
19
+ # route(:bar) do |r|
20
+ # r.is 'foo' do
21
+ # '/bar/foo'
22
+ # end
23
+ # end
24
+ #
25
+ # route do |r|
26
+ # r.on "foo" do
27
+ # route :foo
28
+ # end
29
+ #
30
+ # r.on "bar" do
31
+ # route :bar
32
+ # end
33
+ # end
34
+ #
35
+ # Note that in multi-threaded code, you should not attempt to add a
36
+ # named route after accepting requests.
37
+ module MultiRoute
38
+ # Initialize storage for the named routes.
39
+ def self.configure(app)
40
+ app.instance_exec{@named_routes ||= {}}
41
+ end
42
+
43
+ module ClassMethods
44
+ # Copy the named routes into the subclass when inheriting.
45
+ def inherited(subclass)
46
+ super
47
+ subclass.instance_variable_set(:@named_routes, @named_routes.dup)
48
+ end
49
+
50
+ # Return the named route with the given name.
51
+ def named_route(name)
52
+ @named_routes[name]
53
+ end
54
+
55
+ # If the given route has a named, treat it as a named route and
56
+ # store the route block. Otherwise, this is the main route, so
57
+ # call super.
58
+ def route(name=nil, &block)
59
+ if name
60
+ @named_routes[name] = block
61
+ else
62
+ super(&block)
63
+ end
64
+ end
65
+ end
66
+
67
+ module InstanceMethods
68
+ # Dispatch to the named route with the given name.
69
+ def route(name)
70
+ instance_exec(request, &self.class.named_route(name))
71
+ end
72
+ end
73
+ end
74
+
75
+ register_plugin(:multi_route, MultiRoute)
76
+ end
77
+ end
@@ -0,0 +1,62 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The not_found plugin adds a +not_found+ class method which sets
4
+ # a block that is called whenever a 404 response with an empty body
5
+ # would be returned. The usual use case for this is the desire for
6
+ # nice error pages if the page is not found.
7
+ #
8
+ # You can provide the block with the plugin call:
9
+ #
10
+ # plugin :not_found do
11
+ # "Where did it go?"
12
+ # end
13
+ #
14
+ # Or later via a separate call to +not_found+:
15
+ #
16
+ # plugin :not_found
17
+ #
18
+ # not_found do
19
+ # "Where did it go?"
20
+ # end
21
+ module NotFound
22
+ # If a block is given, install the block as the not_found handler.
23
+ def self.configure(app, &block)
24
+ if block
25
+ app.not_found(&block)
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ # Install the given block as the not_found handler.
31
+ def not_found(&block)
32
+ define_method(:not_found, &block)
33
+ private :not_found
34
+ end
35
+ end
36
+
37
+ module InstanceMethods
38
+ private
39
+
40
+ # If routing returns a 404 response with an empty body, call
41
+ # the not_found handler.
42
+ def _route
43
+ result = super
44
+
45
+ if result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
46
+ super{not_found}
47
+ else
48
+ result
49
+ end
50
+ end
51
+
52
+ # Use an empty not_found_handler by default, so that loading
53
+ # the plugin without defining a not_found handler doesn't
54
+ # break things.
55
+ def not_found
56
+ end
57
+ end
58
+ end
59
+
60
+ register_plugin(:not_found, NotFound)
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The pass plugin adds a request +pass+ method to skip the current +on+
4
+ # block as if it did not match.
5
+ #
6
+ # plugin :pass
7
+ #
8
+ # route do |r|
9
+ # r.on "foo/:bar" do |bar|
10
+ # pass if bar == 'baz'
11
+ # "/foo/#{bar} (not baz)"
12
+ # end
13
+ #
14
+ # r.on "foo/baz" do
15
+ # "/foo/baz"
16
+ # end
17
+ # end
18
+ module Pass
19
+ module RequestMethods
20
+ # Handle passing inside the current block.
21
+ def on(*)
22
+ catch(:pass){super}
23
+ end
24
+
25
+ # Skip the current #on block as if it did not match.
26
+ def pass
27
+ throw :pass
28
+ end
29
+ end
30
+ end
31
+
32
+ register_plugin(:pass, Pass)
33
+ end
34
+ end
@@ -0,0 +1,217 @@
1
+ require "tilt"
2
+
3
+ class Roda
4
+ module RodaPlugins
5
+ # The render plugin adds support for template rendering using the tilt
6
+ # library. Two methods are provided for template rendering, +view+
7
+ # (which uses the layout) and +render+ (which does not).
8
+ #
9
+ # plugin :render
10
+ #
11
+ # route do |r|
12
+ # r.is 'foo' do
13
+ # view('foo') # renders views/foo.erb inside views/layout.erb
14
+ # end
15
+ #
16
+ # r.is 'bar' do
17
+ # render('bar') # renders views/bar.erb
18
+ # end
19
+ # end
20
+ #
21
+ # You can provide options to the plugin method, or later by modifying
22
+ # +render_opts+.
23
+ #
24
+ # plugin :render, :engine=>'haml'
25
+ #
26
+ # render_opts[:views] = 'admin_views'
27
+ #
28
+ # The following options are supported:
29
+ #
30
+ # :cache :: A specific cache to store templates in, or nil/false to not
31
+ # cache templates (useful for development), defaults to true to
32
+ # automatically use the default template cache.
33
+ # :engine :: The tilt engine to use for rendering, defaults to 'erb'.
34
+ # :ext :: The file extension to assume for view files, defaults to the :engine
35
+ # option.
36
+ # :layout :: The base name of the layout file, defaults to 'layout'.
37
+ # :layout_opts :: The options to use when rendering the layout, if different
38
+ # from the default options.
39
+ # :opts :: The tilt options used when rendering templates, defaults to
40
+ # {:outvar=>'@_out_buf'}.
41
+ # :views :: The directory holding the view files, defaults to 'views' in the
42
+ # current directory.
43
+ #
44
+ # Most of these options can be overridden at runtime by passing options
45
+ # to the +view+ or +render+ methods:
46
+ #
47
+ # view('foo', :ext=>'html.erb')
48
+ # render('foo', :views=>'admin_views')
49
+ #
50
+ # There are a couple of additional options to +view+ and +render+ that are
51
+ # available at runtime:
52
+ #
53
+ # :inline :: Use the value given as the template code, instead of looking
54
+ # for template code in a file.
55
+ # :locals :: Hash of local variables to make available inside the template.
56
+ # :path :: Use the value given as the full pathname for the file, instead
57
+ # of using the :views and :ext option in combination with the
58
+ # template name.
59
+ #
60
+ # Here's how those options are used:
61
+ #
62
+ # view(:inline=>'<%= @foo %>')
63
+ # render(:path=>'/path/to/template.erb')
64
+ #
65
+ # If you pass a hash as the first argument to +view+ or +render+, it should
66
+ # have either +:inline+ or +:path+ as one of the keys.
67
+ module Render
68
+ # Default template cache. Thread-safe so that multiple threads can
69
+ # simultaneously use the cache.
70
+ class Cache
71
+ # Mutex used to synchronize access to the cache. Uses a
72
+ # singleton mutex to reduce memory.
73
+ MUTEX = ::Mutex.new
74
+
75
+ # Initialize the cache.
76
+ def initialize
77
+ MUTEX.synchronize{@cache = {}}
78
+ end
79
+
80
+ # Clear the cache.
81
+ alias clear initialize
82
+
83
+ # If the template is found in the cache under the given key,
84
+ # return it, otherwise yield to get the template, and
85
+ # store the template under the given key
86
+ def fetch(key)
87
+ unless template = MUTEX.synchronize{@cache[key]}
88
+ template = yield
89
+ MUTEX.synchronize{@cache[key] = template}
90
+ end
91
+
92
+ template
93
+ end
94
+ end
95
+
96
+ # Setup default rendering options. See Render for details.
97
+ def self.configure(app, opts={})
98
+ if app.opts[:render]
99
+ app.opts[:render].merge!(opts)
100
+ else
101
+ app.opts[:render] = opts.dup
102
+ end
103
+
104
+ opts = app.opts[:render]
105
+ opts[:engine] ||= "erb"
106
+ opts[:ext] = nil unless opts.has_key?(:ext)
107
+ opts[:views] ||= File.expand_path("views", Dir.pwd)
108
+ opts[:layout] = "layout" unless opts.has_key?(:layout)
109
+ opts[:layout_opts] ||= (opts[:layout_opts] || {}).dup
110
+ opts[:opts] ||= (opts[:opts] || {}).dup
111
+ opts[:opts][:outvar] ||= '@_out_buf'
112
+ if RUBY_VERSION >= "1.9"
113
+ opts[:opts][:default_encoding] ||= Encoding.default_external
114
+ end
115
+ cache = opts.fetch(:cache, true)
116
+ opts[:cache] = Cache.new if cache == true
117
+ end
118
+
119
+ module ClassMethods
120
+ # Copy the rendering options into the subclass, duping
121
+ # them as necessary to prevent changes in the subclass
122
+ # affecting the parent class.
123
+ def inherited(subclass)
124
+ super
125
+ opts = subclass.opts[:render] = render_opts.dup
126
+ opts[:layout_opts] = opts[:layout_opts].dup
127
+ opts[:opts] = opts[:opts].dup
128
+ opts[:cache] = Cache.new if opts[:cache]
129
+ end
130
+
131
+ # Return the render options for this class.
132
+ def render_opts
133
+ opts[:render]
134
+ end
135
+ end
136
+
137
+ module InstanceMethods
138
+ # Render the given template. See Render for details.
139
+ def render(template, opts = {}, &block)
140
+ if template.is_a?(Hash)
141
+ if opts.empty?
142
+ opts = template
143
+ else
144
+ opts = opts.merge(template)
145
+ end
146
+ end
147
+ render_opts = render_opts()
148
+
149
+ if content = opts[:inline]
150
+ path = content
151
+ template_block = Proc.new{content}
152
+ template_class = ::Tilt[opts[:engine] || render_opts[:engine]]
153
+ else
154
+ template_class = ::Tilt
155
+ unless path = opts[:path]
156
+ path = template_path(template, opts)
157
+ end
158
+ end
159
+
160
+ cached_template(path) do
161
+ template_class.new(path, 1, render_opts[:opts].merge(opts), &template_block)
162
+ end.render(self, opts[:locals], &block)
163
+ end
164
+
165
+ # Return the render options for the instance's class.
166
+ def render_opts
167
+ self.class.render_opts
168
+ end
169
+
170
+ # Render the given template. If there is a default layout
171
+ # for the class, take the result of the template rendering
172
+ # and render it inside the layout. See Render for details.
173
+ def view(template, opts={})
174
+ if template.is_a?(Hash)
175
+ if opts.empty?
176
+ opts = template
177
+ else
178
+ opts = opts.merge(template)
179
+ end
180
+ end
181
+
182
+ content = render(template, opts)
183
+
184
+ if layout = opts.fetch(:layout, render_opts[:layout])
185
+ if layout_opts = opts[:layout_opts]
186
+ layout_opts = render_opts[:layout_opts].merge(layout_opts)
187
+ end
188
+
189
+ content = render(layout, layout_opts||{}){content}
190
+ end
191
+
192
+ content
193
+ end
194
+
195
+ private
196
+
197
+ # If caching templates, attempt to retrieve the template from the cache. Otherwise, just yield
198
+ # to get the template.
199
+ def cached_template(path, &block)
200
+ if cache = render_opts[:cache]
201
+ cache.fetch(path, &block)
202
+ else
203
+ yield
204
+ end
205
+ end
206
+
207
+ # The path for the given template.
208
+ def template_path(template, opts)
209
+ render_opts = render_opts()
210
+ "#{opts[:views] || render_opts[:views]}/#{template}.#{opts[:ext] || render_opts[:ext] || render_opts[:engine]}"
211
+ end
212
+ end
213
+ end
214
+
215
+ register_plugin(:render, Render)
216
+ end
217
+ end
@@ -0,0 +1,165 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The streaming plugin adds support for streaming responses
4
+ # from roda using the +stream+ method:
5
+ #
6
+ # plugin :streaming
7
+ #
8
+ # route do |r|
9
+ # stream do |out|
10
+ # ['a', 'b', 'c'].each{|v| out << v; sleep 1}
11
+ # end
12
+ # end
13
+ #
14
+ # In order for streaming to work, any webservers used in
15
+ # front of the roda app must not buffer responses.
16
+ #
17
+ # The stream method takes the following options:
18
+ #
19
+ # :callback :: A callback proc to call when the connection is
20
+ # closed.
21
+ # :keep_open :: Whether to keep the connection open after the
22
+ # stream block returns, default is false.
23
+ # :loop :: Whether to call the stream block continuously until
24
+ # the connection is closed.
25
+ #
26
+ # The implementation was originally taken from Sinatra,
27
+ # which is also released under the MIT License:
28
+ #
29
+ # Copyright (c) 2007, 2008, 2009 Blake Mizerany
30
+ # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
31
+ #
32
+ # Permission is hereby granted, free of charge, to any person
33
+ # obtaining a copy of this software and associated documentation
34
+ # files (the "Software"), to deal in the Software without
35
+ # restriction, including without limitation the rights to use,
36
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ # copies of the Software, and to permit persons to whom the
38
+ # Software is furnished to do so, subject to the following
39
+ # conditions:
40
+ #
41
+ # The above copyright notice and this permission notice shall be
42
+ # included in all copies or substantial portions of the Software.
43
+ #
44
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
45
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
46
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
47
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
48
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
49
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
50
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
51
+ # OTHER DEALINGS IN THE SOFTWARE.
52
+ module Streaming
53
+ # Class of the response body in case you use #stream.
54
+ #
55
+ # Three things really matter: The front and back block (back being the
56
+ # block generating content, front the one sending it to the client) and
57
+ # the scheduler, integrating with whatever concurrency feature the Rack
58
+ # handler is using.
59
+ #
60
+ # Scheduler has to respond to defer and schedule.
61
+ class Stream
62
+ include Enumerable
63
+
64
+ # The default scheduler to used when streaming, useful for code
65
+ # using ruby's default threading support.
66
+ class Scheduler
67
+ # Store the stream to schedule.
68
+ def initialize(stream)
69
+ @stream = stream
70
+ end
71
+
72
+ # Immediately yield.
73
+ def defer(*)
74
+ yield
75
+ end
76
+
77
+ # Close the stream if there is an exception when scheduling,
78
+ # and reraise the exception if so.
79
+ def schedule(*)
80
+ yield
81
+ rescue Exception
82
+ @stream.close
83
+ raise
84
+ end
85
+ end
86
+
87
+ # Handle streaming options, see Streaming for details.
88
+ def initialize(opts={}, &back)
89
+ @scheduler = opts[:scheduler] || Scheduler.new(self)
90
+ @back = back.to_proc
91
+ @keep_open = opts[:keep_open]
92
+ @callbacks = []
93
+ @closed = false
94
+
95
+ if opts[:callback]
96
+ callback(&opts[:callback])
97
+ end
98
+ end
99
+
100
+ # Add output to the streaming response body.
101
+ def <<(data)
102
+ @scheduler.schedule{@front.call(data.to_s)}
103
+ self
104
+ end
105
+
106
+ # Add the given block as a callback to call when the block closes.
107
+ def callback(&block)
108
+ return yield if closed?
109
+ @callbacks << block
110
+ end
111
+
112
+ # Alias to callback for EventMachine compatibility.
113
+ alias errback callback
114
+
115
+ # If not already closed, close the connection, and call
116
+ # any callbacks.
117
+ def close
118
+ return if closed?
119
+ @closed = true
120
+ @scheduler.schedule{@callbacks.each{|c| c.call}}
121
+ end
122
+
123
+ # Whether the connection has already been closed.
124
+ def closed?
125
+ @closed
126
+ end
127
+
128
+ # Yield values to the block as they are passed in via #<<.
129
+ def each(&front)
130
+ @front = front
131
+ @scheduler.defer do
132
+ begin
133
+ @back.call(self)
134
+ rescue Exception => e
135
+ @scheduler.schedule{raise e}
136
+ end
137
+ close unless @keep_open
138
+ end
139
+ end
140
+ end
141
+
142
+ module InstanceMethods
143
+ # Immediately return a streaming response using the current response
144
+ # status and headers, calling the block to get the streaming response.
145
+ # See Streaming for details.
146
+ def stream(opts={}, &block)
147
+ opts = opts.merge(:scheduler=>EventMachine) if !opts.has_key?(:scheduler) && env['async.callback']
148
+
149
+ if opts[:loop]
150
+ block = proc do |out|
151
+ until out.closed?
152
+ yield(out)
153
+ end
154
+ end
155
+ end
156
+
157
+ res = response
158
+ request.halt [res.status || 200, res.headers, Stream.new(opts, &block)]
159
+ end
160
+ end
161
+ end
162
+
163
+ register_plugin(:streaming, Streaming)
164
+ end
165
+ end
@@ -0,0 +1,3 @@
1
+ class Roda
2
+ RodaVersion = '0.9.1'.freeze
3
+ end