From ee16f5deb17ec698fd1572988e266ce419a9ebcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Thu, 2 Jan 2020 18:21:07 +0100 Subject: [PATCH 1/5] Add new headers Add Range to allow headers, Content-Range to expose headers and the Accept-Ranges header --- liquor-cabinet.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/liquor-cabinet.rb b/liquor-cabinet.rb index 6f6324f..8746a42 100644 --- a/liquor-cabinet.rb +++ b/liquor-cabinet.rb @@ -68,8 +68,9 @@ class LiquorCabinet < Sinatra::Base before path do headers 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Methods' => 'GET, PUT, DELETE', - 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match', - 'Access-Control-Expose-Headers' => 'ETag, Content-Length' + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match, Range', + 'Access-Control-Expose-Headers' => 'ETag, Content-Length, Content-Range', + 'Accept-Ranges' => 'bytes' headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"] headers['Cache-Control'] = 'no-cache' From da7f077300da297004d5e7847581a8bd48e1e8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Thu, 2 Jan 2020 18:23:47 +0100 Subject: [PATCH 2/5] Pass the Range HTTP header to GET requests if present --- lib/remote_storage/s3.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/remote_storage/s3.rb b/lib/remote_storage/s3.rb index 6233cff..c852006 100644 --- a/lib/remote_storage/s3.rb +++ b/lib/remote_storage/s3.rb @@ -32,8 +32,10 @@ module RemoteStorage def do_get_request(url, &block) deal_with_unauthorized_requests do + headers = { } + headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"] authorization_headers = authorization_headers_for("GET", url) - RestClient.get(url, authorization_headers, &block) + RestClient.get(url, authorization_headers.merge(headers), &block) end end From 8c95e804ca0df37d03e12234f67c6449a843ddbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Thu, 2 Jan 2020 18:24:12 +0100 Subject: [PATCH 3/5] Add support for partial responses Set the Content-Range response header and return a 206 status if it is a partial response (a Range was passed) --- lib/remote_storage/rest_provider.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/remote_storage/rest_provider.rb b/lib/remote_storage/rest_provider.rb index c04ae59..f50042d 100644 --- a/lib/remote_storage/rest_provider.rb +++ b/lib/remote_storage/rest_provider.rb @@ -69,6 +69,11 @@ module RemoteStorage res = do_get_request(url) + if res.headers[:content_range] + # Partial content + server.headers["Content-Range"] = res.headers[:content_range] + server.status 206 + end set_response_headers(metadata) return res.body From 331fdbe1c7eb5064262c308d089d134948d1190a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Fri, 3 Jan 2020 18:17:19 +0100 Subject: [PATCH 4/5] Add specs for public resources, including getting partial content --- spec/s3/app_spec.rb | 8 ++++++++ spec/shared_examples.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/spec/s3/app_spec.rb b/spec/s3/app_spec.rb index 65950d2..64f2e3d 100644 --- a/spec/s3/app_spec.rb +++ b/spec/s3/app_spec.rb @@ -16,12 +16,20 @@ describe "S3 provider" do stub_request(:put, "#{container_url_for("phil")}/food/aguacate"). with(body: "aye"). to_return(status: 200, headers: { etag: '"0915etag"', date: "Fri, 04 Mar 2016 12:20:18 GMT" }) + stub_request(:put, "#{container_url_for("phil")}/public/shares/example.jpg"). + to_return(status: 200, headers: { etag: '"0817etag"', content_type: "image/jpeg", date: "Fri, 04 Mar 2016 12:20:18 GMT" }) + stub_request(:put, "#{container_url_for("phil")}/public/shares/example_partial.jpg"). + to_return(status: 200, headers: { etag: '"0817etag"', content_type: "image/jpeg", date: "Fri, 04 Mar 2016 12:20:18 GMT" }) stub_request(:head, "#{container_url_for("phil")}/food/aguacate"). to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) stub_request(:get, "#{container_url_for("phil")}/food/aguacate"). to_return(status: 200, body: "rootbody", headers: { etag: '"0817etag"', content_type: "text/plain; charset=utf-8" }) stub_request(:delete, "#{container_url_for("phil")}/food/aguacate"). to_return(status: 200, headers: { etag: '"0815etag"' }) + stub_request(:get, "#{container_url_for("phil")}/public/shares/example.jpg"). + to_return(status: 200, body: "", headers: { etag: '"0817etag"', content_type: "image/jpeg" }) + stub_request(:get, "#{container_url_for("phil")}/public/shares/example_partial.jpg"). + to_return(status: 206, body: "", headers: { etag: '"0817etag"', content_type: "image/jpeg", content_range: "bytes 0-16/128" }) # Write new content to check the metadata in Redis stub_request(:put, "#{container_url_for("phil")}/food/banano"). diff --git a/spec/shared_examples.rb b/spec/shared_examples.rb index aa8f997..1613fee 100644 --- a/spec/shared_examples.rb +++ b/spec/shared_examples.rb @@ -397,6 +397,47 @@ shared_examples_for 'a REST adapter' do purge_redis end + context "requests to public resources" do + before do + redis.sadd "authorizations:phil:amarillo", [":rw"] + header "Authorization", "Bearer amarillo" + end + + describe "normal request" do + before do + header "Content-Type", "image/jpeg" + + put "/phil/public/shares/example.jpg", "" + end + + it "returns the required response headers" do + get "/phil/public/shares/example.jpg" + + last_response.status.must_equal 200 + last_response.headers["Content-Type"].must_equal "image/jpeg" + end + end + + describe "partial request" do + before do + header "Content-Type", "image/jpeg" + + put "/phil/public/shares/example_partial.jpg", <<-EOF +JFIFddDuckyA␍⎺␉␊␍ +#%'%#//33//@@@@@@@@@@@@@@@&&0##0+.'''.+550055@@?@@@@@@@@@@@>"!1AQaq"2B + EOF + end + + it "returns the required response headers" do + header 'Range', 'bytes=0-16' + get "/phil/public/shares/example_partial.jpg" + + last_response.status.must_equal 206 + last_response.headers["Content-Type"].must_equal "image/jpeg" + end + end + end + context "not authorized" do describe "without token" do From 1824766a03cb798bdbd7689eb207fa98ece0b142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Mon, 6 Jan 2020 14:03:26 +0100 Subject: [PATCH 5/5] Implement Range support for the Swift provider, with specs --- lib/remote_storage/rest_provider.rb | 4 +++- spec/swift/app_spec.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/remote_storage/rest_provider.rb b/lib/remote_storage/rest_provider.rb index f50042d..7447c53 100644 --- a/lib/remote_storage/rest_provider.rb +++ b/lib/remote_storage/rest_provider.rb @@ -410,7 +410,9 @@ module RemoteStorage def do_get_request(url, &block) deal_with_unauthorized_requests do - RestClient.get(url, default_headers, &block) + headers = { } + headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"] + RestClient.get(url, default_headers.merge(headers), &block) end end diff --git a/spec/swift/app_spec.rb b/spec/swift/app_spec.rb index eff8bd9..9871b4b 100644 --- a/spec/swift/app_spec.rb +++ b/spec/swift/app_spec.rb @@ -16,12 +16,20 @@ describe "Swift provider" do stub_request(:put, "#{container_url_for("phil")}/food/aguacate"). with(body: "aye"). to_return(status: 200, headers: { etag: "0915etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) + stub_request(:put, "#{container_url_for("phil")}/public/shares/example.jpg"). + to_return(status: 200, headers: { etag: '"0817etag"', content_type: "image/jpeg", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) + stub_request(:put, "#{container_url_for("phil")}/public/shares/example_partial.jpg"). + to_return(status: 200, headers: { etag: '"0817etag"', content_type: "image/jpeg", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) stub_request(:head, "#{container_url_for("phil")}/food/aguacate"). to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) stub_request(:get, "#{container_url_for("phil")}/food/aguacate"). to_return(status: 200, body: "rootbody", headers: { etag: "0817etag", content_type: "text/plain; charset=utf-8" }) stub_request(:delete, "#{container_url_for("phil")}/food/aguacate"). to_return(status: 200, headers: { etag: "0815etag" }) + stub_request(:get, "#{container_url_for("phil")}/public/shares/example.jpg"). + to_return(status: 200, body: "", headers: { etag: '"0817etag"', content_type: "image/jpeg" }) + stub_request(:get, "#{container_url_for("phil")}/public/shares/example_partial.jpg"). + to_return(status: 206, body: "", headers: { etag: '"0817etag"', content_type: "image/jpeg", content_range: "bytes 0-16/128" }) # Write new content to check the metadata in Redis stub_request(:put, "#{container_url_for("phil")}/food/banano").