Skip to content

Commit 3db3b0a

Browse files
committed
un-deprecate stream-like objects
- deprecate file and replace with sendfile - update stream to not deprecate stream-like objects
1 parent 2bf2c1a commit 3db3b0a

14 files changed

+329
-74
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
### 1.3.4 (Next)
1+
### 1.4.0 (Next)
22

33
#### Features
44

5+
* [#1520](https://github.com/ruby-grape/grape/pull/1520): Un-deprecate stream-like objects - [@urkle](https://github.com/urkle).
56
* [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock).
67
* [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock).
78
* Your contribution here.

README.md

+25-3
Original file line numberDiff line numberDiff line change
@@ -3168,17 +3168,19 @@ end
31683168
31693169
Use `body false` to return `204 No Content` without any data or content-type.
31703170
3171-
You can also set the response to a file with `file`.
3171+
You can also set the response to a file with `sendfile`. This works with the
3172+
[Rack::Sendfile](https://www.rubydoc.info/gems/rack/Rack/Sendfile) middleware to optimally send
3173+
the file through your web server software.
31723174
31733175
```ruby
31743176
class API < Grape::API
31753177
get '/' do
3176-
file '/path/to/file'
3178+
sendfile '/path/to/file'
31773179
end
31783180
end
31793181
```
31803182
3181-
If you want a file to be streamed using Rack::Chunked, use `stream`.
3183+
To stream a file in chunks use `stream`
31823184
31833185
```ruby
31843186
class API < Grape::API
@@ -3188,6 +3190,26 @@ class API < Grape::API
31883190
end
31893191
```
31903192
3193+
If you want to stream non-file data use the `stream` method and a `Stream` object.
3194+
This is an object that responds to `each` and yields for each chunk to send to the client.
3195+
Each chunk will be sent as it is yielded instead of waiting for all of the content to be available.
3196+
3197+
```ruby
3198+
class MyStream
3199+
def each
3200+
yield 'part 1'
3201+
yield 'part 2'
3202+
yield 'part 3'
3203+
end
3204+
end
3205+
3206+
class API < Grape::API
3207+
get '/' do
3208+
stream MyStream.new
3209+
end
3210+
end
3211+
```
3212+
31913213
## Authentication
31923214
31933215
### Basic and Digest Auth

UPGRADING.md

+64
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,70 @@
11
Upgrading Grape
22
===============
33

4+
### Upgrading to >= 1.4.0
5+
6+
#### Reworking stream and file and un-deprecating stream like-objects
7+
8+
Previously in 0.16 stream-like objects were deprecated. This release restores their functionality for use-cases other than file streaming.
9+
10+
This release deprecated `file` in favor of `sendfile` to better document its purpose.
11+
12+
To deliver a file via the Sendfile support in your web server and have the Rack::Sendfile middleware enabled. See [`Rack::Sendfile`](https://www.rubydoc.info/gems/rack/Rack/Sendfile).
13+
```ruby
14+
class API < Grape::API
15+
get '/' do
16+
sendfile '/path/to/file'
17+
end
18+
end
19+
```
20+
21+
Use `stream` to stream file content in chunks.
22+
23+
```ruby
24+
class API < Grape::API
25+
get '/' do
26+
stream '/path/to/file'
27+
end
28+
end
29+
```
30+
31+
Or use `stream` to stream other kinds of content. In the following example a streamer class
32+
streams paginated data from a database.
33+
34+
```ruby
35+
class MyObject
36+
attr_accessor :result
37+
38+
def initialize(query)
39+
@result = query
40+
end
41+
42+
def each
43+
yield '['
44+
# Do paginated DB fetches and return each page formatted
45+
first = false
46+
result.find_in_batches do |records|
47+
yield process_records(records, first)
48+
first = false
49+
end
50+
yield ']'
51+
end
52+
53+
def process_records(records, first)
54+
buffer = +''
55+
buffer << ',' unless first
56+
buffer << records.map(&:to_json).join(',')
57+
buffer
58+
end
59+
end
60+
61+
class API < Grape::API
62+
get '/' do
63+
stream MyObject.new(Sprocket.all)
64+
end
65+
end
66+
```
67+
468
### Upgrading to >= 1.3.3
569

670
#### Nil values for structures

lib/grape.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,12 @@ module Presenters
206206
end
207207
end
208208

209-
module ServeFile
209+
module ServeStream
210210
extend ::ActiveSupport::Autoload
211211
eager_autoload do
212-
autoload :FileResponse
213212
autoload :FileBody
214213
autoload :SendfileResponse
214+
autoload :StreamResponse
215215
end
216216
end
217217
end

lib/grape/dsl/inside_route.rb

+31-9
Original file line numberDiff line numberDiff line change
@@ -279,23 +279,36 @@ def return_no_content
279279
body false
280280
end
281281

282-
# Allows you to define the response as a file-like object.
282+
# Deprecated method to send files to the client. Use `sendfile` or `stream`
283+
def file(value = nil)
284+
if value.is_a?(String)
285+
warn '[DEPRECATION] Use sendfile or stream to send files.'
286+
sendfile(value)
287+
elsif !value.is_a?(NilClass)
288+
warn '[DEPRECATION] Use stream to use a Stream object.'
289+
stream(value)
290+
else
291+
warn '[DEPRECATION] Use sendfile or stream to send files.'
292+
sendfile
293+
end
294+
end
295+
296+
# Allows you to send a file to the client via sendfile.
283297
#
284298
# @example
285299
# get '/file' do
286-
# file FileStreamer.new(...)
300+
# sendfile FileStreamer.new(...)
287301
# end
288302
#
289303
# GET /file # => "contents of file"
290-
def file(value = nil)
304+
def sendfile(value = nil)
291305
if value.is_a?(String)
292-
file_body = Grape::ServeFile::FileBody.new(value)
293-
@file = Grape::ServeFile::FileResponse.new(file_body)
306+
file_body = Grape::ServeStream::FileBody.new(value)
307+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
294308
elsif !value.is_a?(NilClass)
295-
warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
296-
@file = Grape::ServeFile::FileResponse.new(value)
309+
raise ArgumentError, 'Argument must be a file path'
297310
else
298-
instance_variable_defined?(:@file) ? @file : nil
311+
stream
299312
end
300313
end
301314

@@ -318,7 +331,16 @@ def stream(value = nil)
318331
header 'Content-Length', nil
319332
header 'Transfer-Encoding', nil
320333
header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
321-
file(value)
334+
if value.is_a?(String)
335+
file_body = Grape::ServeStream::FileBody.new(value)
336+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
337+
elsif value.respond_to?(:each)
338+
@stream = Grape::ServeStream::StreamResponse.new(value)
339+
elsif !value.is_a?(NilClass)
340+
raise ArgumentError, 'Stream object must respond to :each.'
341+
else
342+
instance_variable_defined?(:@stream) ? @stream : nil
343+
end
322344
end
323345

324346
# Allows you to make use of Grape Entities by setting

lib/grape/endpoint.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def initialize(new_settings, options = {}, &block)
9999
@block = nil
100100

101101
@status = nil
102-
@file = nil
102+
@stream = nil
103103
@body = nil
104104
@proc = nil
105105

@@ -271,8 +271,8 @@ def run
271271
# status verifies body presence when DELETE
272272
@body ||= response_object
273273

274-
# The body commonly is an Array of Strings, the application instance itself, or a File-like object
275-
response_object = file || [body]
274+
# The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
275+
response_object = stream || [body]
276276

277277
[status, header, response_object]
278278
ensure

lib/grape/middleware/formatter.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ def after
3636
def build_formatted_response(status, headers, bodies)
3737
headers = ensure_content_type(headers)
3838

39-
if bodies.is_a?(Grape::ServeFile::FileResponse)
40-
Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp|
41-
resp.body = bodies.file
39+
if bodies.is_a?(Grape::ServeStream::StreamResponse)
40+
Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
41+
resp.body = bodies.stream
4242
end
4343
else
4444
# Allow content-type to be explicitly overwritten

lib/grape/serve_file/file_body.rb renamed to lib/grape/serve_stream/file_body.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
module Grape
4-
module ServeFile
4+
module ServeStream
55
CHUNK_SIZE = 16_384
66

77
# Class helps send file through API

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
module Grape
4-
module ServeFile
4+
module ServeStream
55
# Response should respond to to_path method
66
# for using Rack::SendFile middleware
77
class SendfileResponse < Rack::Response

lib/grape/serve_file/file_response.rb renamed to lib/grape/serve_stream/stream_response.rb

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
# frozen_string_literal: true
22

33
module Grape
4-
module ServeFile
5-
# A simple class used to identify responses which represent files and do not
4+
module ServeStream
5+
# A simple class used to identify responses which represent streams (or files) and do not
66
# need to be formatted or pre-read by Rack::Response
7-
class FileResponse
8-
attr_reader :file
7+
class StreamResponse
8+
attr_reader :stream
99

10-
# @param file [Object]
11-
def initialize(file)
12-
@file = file
10+
# @param stream [Object]
11+
def initialize(stream)
12+
@stream = stream
1313
end
1414

1515
# Equality provided mostly for tests.
1616
#
1717
# @return [Boolean]
1818
def ==(other)
19-
file == other.file
19+
stream == other.stream
2020
end
2121
end
2222
end

lib/grape/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module Grape
44
# The current version of Grape.
5-
VERSION = '1.3.4'
5+
VERSION = '1.4.0'
66
end

0 commit comments

Comments
 (0)