Skip to content

Commit d3641b5

Browse files
committed
Serve files without using FileStreamer-like object
1 parent 41808e3 commit d3641b5

File tree

11 files changed

+103
-70
lines changed

11 files changed

+103
-70
lines changed

.rubocop_todo.yml

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2016-01-08 12:24:37 -0600 using RuboCop version 0.35.1.
3+
# on 2016-03-14 21:22:57 +0300 using RuboCop version 0.35.1.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
@@ -11,39 +11,39 @@ Lint/NestedMethodDefinition:
1111
Exclude:
1212
- 'lib/grape/util/strict_hash_configuration.rb'
1313

14-
# Offense count: 37
14+
# Offense count: 39
1515
Metrics/AbcSize:
16-
Max: 51
16+
Max: 44
1717

1818
# Offense count: 1
1919
Metrics/BlockNesting:
2020
Max: 4
2121

22-
# Offense count: 5
22+
# Offense count: 6
2323
# Configuration parameters: CountComments.
2424
Metrics/ClassLength:
2525
Max: 281
2626

27-
# Offense count: 23
27+
# Offense count: 24
2828
Metrics/CyclomaticComplexity:
2929
Max: 14
3030

31-
# Offense count: 759
31+
# Offense count: 853
3232
# Configuration parameters: AllowURI, URISchemes.
3333
Metrics/LineLength:
3434
Max: 215
3535

36-
# Offense count: 45
36+
# Offense count: 48
3737
# Configuration parameters: CountComments.
3838
Metrics/MethodLength:
3939
Max: 36
4040

4141
# Offense count: 8
4242
# Configuration parameters: CountComments.
4343
Metrics/ModuleLength:
44-
Max: 272
44+
Max: 277
4545

46-
# Offense count: 16
46+
# Offense count: 15
4747
Metrics/PerceivedComplexity:
4848
Max: 16
4949

@@ -57,12 +57,12 @@ Style/BlockDelimiters:
5757
- 'spec/grape/middleware/versioner/header_spec.rb'
5858
- 'spec/grape/request_spec.rb'
5959

60-
# Offense count: 105
60+
# Offense count: 111
6161
# Configuration parameters: Exclude.
6262
Style/Documentation:
6363
Enabled: false
6464

65-
# Offense count: 7
65+
# Offense count: 6
6666
Style/DoubleNegation:
6767
Exclude:
6868
- 'lib/grape/api.rb'

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
0.15.1 (Next)
22
=============
33

4+
* [#1321](https://github.com/ruby-grape/grape/pull/1321): Serve files without using FileStreamer-like object - [@lfidnl](https://github.com/lfidnl).
45
* Your contribution here.
56

67
0.15.0 (3/8/2016)

README.md

+4-42
Original file line numberDiff line numberDiff line change
@@ -2437,61 +2437,23 @@ end
24372437
24382438
Use `body false` to return `204 No Content` without any data or content-type.
24392439
2440-
You can also set the response to a file-like object with `file`.
2441-
Note: Rack will read your entire Enumerable before returning a response. If
2442-
you would like to stream the response, see `stream`.
2440+
You can also set the response to a file with `file`.
24432441
24442442
```ruby
2445-
class FileStreamer
2446-
def initialize(file_path)
2447-
@file_path = file_path
2448-
end
2449-
2450-
def each(&blk)
2451-
File.open(@file_path, 'rb') do |file|
2452-
file.each(10, &blk)
2453-
end
2454-
end
2455-
end
2456-
24572443
class API < Grape::API
24582444
get '/' do
2459-
file FileStreamer.new('file.bin')
2445+
file '/path/to/file'
24602446
end
24612447
end
24622448
```
24632449
2464-
If you want a file-like object to be streamed using Rack::Chunked, use `stream`.
2450+
If you want a file to be streamed using Rack::Chunked, use `stream`.
24652451
24662452
```ruby
24672453
class API < Grape::API
24682454
get '/' do
2469-
stream FileStreamer.new('file.bin')
2470-
end
2471-
end
2472-
```
2473-
2474-
If you want to take advantage of `Rack::Sendfile`, which intercepts responses whose body is
2475-
being served from a file and replaces it with a server specific X-Sendfile header, specify `to_path`
2476-
method in your file streamer class which returns path of served file:
2477-
2478-
```ruby
2479-
class FileStreamer
2480-
# ...
2481-
2482-
def to_path
2483-
@file_path
2455+
stream '/path/to/file'
24842456
end
2485-
2486-
# ...
2487-
end
2488-
```
2489-
2490-
Note: don't forget turn on `Rack::Sendfile` middleware in your API:
2491-
2492-
```ruby
2493-
class API < Grape::API
2494-
use Rack::Sendfile
24952457
end
24962458
```
24972459

lib/grape.rb

+7-2
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ module Util
131131
autoload :StackableValues
132132
autoload :InheritableSetting
133133
autoload :StrictHashConfiguration
134-
autoload :FileResponse
135-
autoload :SendfileResponse
136134
end
137135

138136
module DSL
@@ -163,6 +161,13 @@ module Presenters
163161
extend ActiveSupport::Autoload
164162
autoload :Presenter
165163
end
164+
165+
module ServeFile
166+
extend ActiveSupport::Autoload
167+
autoload :FileResponse
168+
autoload :FileBody
169+
autoload :SendfileResponse
170+
end
166171
end
167172

168173
require 'grape/util/content_types'

lib/grape/dsl/inside_route.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,12 @@ def body(value = nil)
191191
#
192192
# GET /file # => "contents of file"
193193
def file(value = nil)
194-
if value
195-
@file = Grape::Util::FileResponse.new(value)
194+
if value.is_a?(String)
195+
file_body = Grape::ServeFile::FileBody.new(value)
196+
@file = Grape::ServeFile::FileResponse.new(file_body)
197+
elsif !value.is_a?(NilClass)
198+
warn '[DEPRECATION] Argument as FileStreamer-like object will be deprecated. Use path to file instead'
199+
@file = Grape::ServeFile::FileResponse.new(value)
196200
else
197201
@file
198202
end

lib/grape/middleware/formatter.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def after
3434
def build_formatted_response(status, headers, bodies)
3535
headers = ensure_content_type(headers)
3636

37-
if bodies.is_a?(Grape::Util::FileResponse)
38-
Grape::Util::SendfileResponse.new([], status, headers) do |resp|
37+
if bodies.is_a?(Grape::ServeFile::FileResponse)
38+
Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp|
3939
resp.body = bodies.file
4040
end
4141
else

lib/grape/serve_file/file_body.rb

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Grape
2+
module ServeFile
3+
CHUNK_SIZE = 16_384
4+
5+
# Class helps send file through API
6+
class FileBody
7+
attr_reader :path
8+
9+
# @param path [String]
10+
def initialize(path)
11+
@path = path
12+
end
13+
14+
# Need for Rack::Sendfile middleware
15+
#
16+
# @return [String]
17+
def to_path
18+
path
19+
end
20+
21+
def each
22+
File.open(path, 'rb') do |file|
23+
while (chunk = file.read(CHUNK_SIZE))
24+
yield chunk
25+
end
26+
end
27+
end
28+
29+
def ==(other)
30+
path == other.path
31+
end
32+
end
33+
end
34+
end

lib/grape/util/file_response.rb renamed to lib/grape/serve_file/file_response.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Grape
2-
module Util
2+
module ServeFile
33
# A simple class used to identify responses which represent files and do not
44
# need to be formatted or pre-read by Rack::Response
55
class FileResponse

lib/grape/util/sendfile_response.rb renamed to lib/grape/serve_file/sendfile_response.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Grape
2-
module Util
2+
module ServeFile
33
# Response should respond to to_path method
44
# for using Rack::SendFile middleware
55
class SendfileResponse < Rack::Response

spec/grape/dsl/inside_route_spec.rb

+34-7
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,37 @@ def initialize
179179

180180
describe '#file' do
181181
describe 'set' do
182-
before do
183-
subject.file 'file'
182+
context 'as file path' do
183+
let(:file_path) { '/some/file/path' }
184+
185+
let(:file_response) do
186+
file_body = Grape::ServeFile::FileBody.new(file_path)
187+
Grape::ServeFile::FileResponse.new(file_body)
188+
end
189+
190+
before do
191+
subject.file file_path
192+
end
193+
194+
it 'returns value wrapped in FileResponse' do
195+
expect(subject.file).to eq file_response
196+
end
184197
end
185198

186-
it 'returns value wrapped in FileResponse' do
187-
expect(subject.file).to eq Grape::Util::FileResponse.new('file')
199+
context 'as object (backward compatibility)' do
200+
let(:file_object) { Class.new }
201+
202+
let(:file_response) do
203+
Grape::ServeFile::FileResponse.new(file_object)
204+
end
205+
206+
before do
207+
subject.file file_object
208+
end
209+
210+
it 'returns value wrapped in FileResponse' do
211+
expect(subject.file).to eq file_response
212+
end
188213
end
189214
end
190215

@@ -195,19 +220,21 @@ def initialize
195220

196221
describe '#stream' do
197222
describe 'set' do
223+
let(:file_object) { Class.new }
224+
198225
before do
199226
subject.header 'Cache-Control', 'cache'
200227
subject.header 'Content-Length', 123
201228
subject.header 'Transfer-Encoding', 'base64'
202-
subject.stream 'file'
229+
subject.stream file_object
203230
end
204231

205232
it 'returns value wrapped in FileResponse' do
206-
expect(subject.stream).to eq Grape::Util::FileResponse.new('file')
233+
expect(subject.stream).to eq Grape::ServeFile::FileResponse.new(file_object)
207234
end
208235

209236
it 'also sets result of file to value wrapped in FileResponse' do
210-
expect(subject.file).to eq Grape::Util::FileResponse.new('file')
237+
expect(subject.file).to eq Grape::ServeFile::FileResponse.new(file_object)
211238
end
212239

213240
it 'sets Cache-Control header to no-cache' do

spec/grape/middleware/formatter_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,9 @@ def to_xml
287287
let(:app) { ->(_env) { [200, {}, @body] } }
288288

289289
it 'returns Grape::Uril::SendFileReponse' do
290-
@body = Grape::Util::FileResponse.new('file')
290+
@body = Grape::ServeFile::FileResponse.new('file')
291291
env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }
292-
expect(subject.call(env)).to be_a(Grape::Util::SendfileResponse)
292+
expect(subject.call(env)).to be_a(Grape::ServeFile::SendfileResponse)
293293
end
294294
end
295295
end

0 commit comments

Comments
 (0)