From 0c827c1c0e4aa8e5bf6a465541b4a0d7e0293a5d Mon Sep 17 00:00:00 2001 From: Ivan Kabluchkov Date: Fri, 19 Feb 2016 18:21:23 +0300 Subject: [PATCH] Support Rack::Sendfile middleware --- CHANGELOG.md | 1 + README.md | 24 +++++++++++ lib/grape.rb | 1 + lib/grape/middleware/formatter.rb | 2 +- lib/grape/util/sendfile_response.rb | 19 +++++++++ spec/grape/integration/rack_sendfile_spec.rb | 44 ++++++++++++++++++++ spec/grape/middleware/formatter_spec.rb | 10 +++++ 7 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 lib/grape/util/sendfile_response.rb create mode 100644 spec/grape/integration/rack_sendfile_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index f01f1d3b24..aa62b550e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [#1252](https://github.com/ruby-grape/grape/pull/1252): Allow default to be a subset or equal to allowed values without raising IncompatibleOptionValues - [@jeradphelps](https://github.com/jeradphelps). * [#1255](https://github.com/ruby-grape/grape/pull/1255): Allow param type definition in `route_param` - [@namusyaka](https://github.com/namusyaka). * [#1257](https://github.com/ruby-grape/grape/pull/1257): Allow Proc, Symbol or String in `rescue_from with: ...` - [@namusyaka](https://github.com/namusyaka). +* [#1280](https://github.com/ruby-grape/grape/pull/1280): Support `Rack::Sendfile` middleware - [@lfidnl](https://github.com/lfidnl). * [#1285](https://github.com/ruby-grape/grape/pull/1285): Add a warning for errors appearing in `after` callbacks - [@gregormelhorn](https://github.com/gregormelhorn). * Your contribution here. diff --git a/README.md b/README.md index d62571c082..e276d5939b 100644 --- a/README.md +++ b/README.md @@ -2328,6 +2328,30 @@ class API < Grape::API end ``` +If you want to take advantage of `Rack::Sendfile`, which intercepts responses whose body is +being served from a file and replaces it with a server specific X-Sendfile header, specify `to_path` +method in your file streamer class which returns path of served file: + +```ruby +class FileStreamer + # ... + + def to_path + @file_path + end + + # ... +end +``` + +Note: don't forget turn on `Rack::Sendfile` middleware in your API: + +```ruby +class API < Grape::API + use Rack::Sendfile +end +``` + ## Authentication ### Basic and Digest Auth diff --git a/lib/grape.rb b/lib/grape.rb index 4a493e9709..c3a8cd5491 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -131,6 +131,7 @@ module Util autoload :InheritableSetting autoload :StrictHashConfiguration autoload :FileResponse + autoload :SendfileResponse end module DSL diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 41d2e11ce4..39244af3a3 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -35,7 +35,7 @@ def build_formatted_response(status, headers, bodies) headers = ensure_content_type(headers) if bodies.is_a?(Grape::Util::FileResponse) - Rack::Response.new([], status, headers) do |resp| + Grape::Util::SendfileResponse.new([], status, headers) do |resp| resp.body = bodies.file end else diff --git a/lib/grape/util/sendfile_response.rb b/lib/grape/util/sendfile_response.rb new file mode 100644 index 0000000000..aca48b5950 --- /dev/null +++ b/lib/grape/util/sendfile_response.rb @@ -0,0 +1,19 @@ +module Grape + module Util + # Response should respond to to_path method + # for using Rack::SendFile middleware + class SendfileResponse < Rack::Response + def respond_to?(method_name, include_all = false) + if method_name == :to_path + @body.respond_to?(:to_path, include_all) + else + super + end + end + + def to_path + @body.to_path + end + end + end +end diff --git a/spec/grape/integration/rack_sendfile_spec.rb b/spec/grape/integration/rack_sendfile_spec.rb new file mode 100644 index 0000000000..3136f7f21a --- /dev/null +++ b/spec/grape/integration/rack_sendfile_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe Rack::Sendfile do + subject do + send_file = file_streamer + app = Class.new(Grape::API) do + use Rack::Sendfile + format :json + get do + file send_file + end + end + + options = { + method: 'GET', + 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect', + 'HTTP_X_ACCEL_MAPPING' => '/accel/mapping/=/replaced/' + } + env = Rack::MockRequest.env_for('/', options) + app.call(env) + end + + context do + let(:file_streamer) do + double(:file_streamer, to_path: '/accel/mapping/some/path') + end + + it 'contains Sendfile headers' do + headers = subject[1] + expect(headers).to include('X-Accel-Redirect') + end + end + + context do + let(:file_streamer) do + double(:file_streamer) + end + + it 'not contains Sendfile headers' do + headers = subject[1] + expect(headers).to_not include('X-Accel-Redirect') + end + end +end diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index 3e27cbe333..76fc44ddcf 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -282,4 +282,14 @@ def to_xml end end end + + context 'send file' do + let(:app) { ->(_env) { [200, {}, @body] } } + + it 'returns Grape::Uril::SendFileReponse' do + @body = Grape::Util::FileResponse.new('file') + env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' } + expect(subject.call(env)).to be_a(Grape::Util::SendfileResponse) + end + end end