diff --git a/CHANGELOG.md b/CHANGELOG.md index 891eade53a..25bec0abb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [#952](/~https://github.com/intridea/grape/pull/952): Status method now raises error when called with invalid status code - [@dabrorius](/~https://github.com/dabrorius). * [#957](/~https://github.com/intridea/grape/pull/957): Regexp validator now supports `allow_blank`, `nil` value behavior changed - [@calfzhou](https://giihub.com/calfzhou). * [#962](/~https://github.com/intridea/grape/pull/962): The `default` attribute with `false` value is documented now - [@ajvondrak](/~https://github.com/ajvondrak). +* [#](/~https://github.com/intridea/grape/pull/): Added `file` method, explicitly setting a file-like response object - [@dblock](/~https://github.com/dblock). #### Fixes diff --git a/README.md b/README.md index a1d26a6571..551afb998d 100644 --- a/README.md +++ b/README.md @@ -2009,7 +2009,7 @@ class API < Grape::API end ``` -You can also set the response body explicitly with `body`. +You can set the response body explicitly with `body`. ```ruby class API < Grape::API @@ -2023,6 +2023,28 @@ end Use `body false` to return `204 No Content` without any data or content-type. +You can also set the response to a file-like object with `file`. + +```ruby +class FileStreamer + def initialize(file_path) + @file_path = file_path + end + + def each(&blk) + File.open(@file_path, 'rb') do |file| + file.each(10, &blk) + end + end +end + +class API < Grape::API + get '/' do + file FileStreamer.new('file.bin') + end +end +``` + ## Authentication ### Basic and Digest Auth diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 08c0a1c123..9f2502637b 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -169,6 +169,22 @@ def body(value = nil) end end + # Allows you to define the response as a file-like object. + # + # @example + # get '/file' do + # file FileStreamer.new(...) + # end + # + # GET /file # => "contents of file" + def file(value = nil) + if value + @file = value + else + @file + end + end + # Allows you to make use of Grape Entities by setting # the response body to the serializable hash of the # entity provided in the `:with` option. This has the diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 977117cce4..35fc301aae 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -248,11 +248,13 @@ def run(env) run_filters after_validations - response_text = @block ? @block.call(self) : nil + response_object = @block ? @block.call(self) : nil run_filters afters cookies.write(header) - [status, header, [body || response_text]] + # The Body commonly is an Array of Strings, the application instance itself, or a File-like object. + response_object = file || [body || response_object] + [status, header, response_object] end def build_middleware diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index d31ea05266..5c97daeee3 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -29,9 +29,9 @@ def after api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env['api.format'] formatter = Grape::Formatter::Base.formatter_for api_format, options begin - bodymap = bodies.collect do |body| + bodymap = bodies.respond_to?(:collect) ? bodies.collect do |body| formatter.call body, env - end + end : bodies rescue Grape::Exceptions::InvalidFormatter => e throw :error, status: 500, message: e.message end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 528a28b396..dfd303c13e 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -927,4 +927,18 @@ def memoized expect(last_response.status).to eq(406) end end + + context 'binary' do + before do + subject.get do + file FileStreamer.new(__FILE__) + end + end + + it 'suports stream objects in response' do + get '/' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq File.read(__FILE__) + end + end end diff --git a/spec/support/file_streamer.rb b/spec/support/file_streamer.rb new file mode 100644 index 0000000000..8a9f24d090 --- /dev/null +++ b/spec/support/file_streamer.rb @@ -0,0 +1,11 @@ +class FileStreamer + def initialize(file_path) + @file_path = file_path + end + + def each(&blk) + File.open(@file_path, 'rb') do |file| + file.each(10, &blk) + end + end +end