http_range 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -0
- data/SPEC.md +25 -0
- data/http_range.gemspec +5 -3
- data/lib/http_range/middleware/accept_ranges.rb +80 -0
- data/lib/http_range/parser.rb +2 -0
- data/lib/http_range/version.rb +1 -1
- data/lib/http_range.rb +1 -0
- data/spec/http_range_spec.rb +7 -1
- data/spec/middleware/accept_ranges_spec.rb +41 -0
- data/spec/spec_helper.rb +1 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d22f33d1f2262be5ab34420858513ef205e2c7f8
|
4
|
+
data.tar.gz: 15d172574a2cc0addaad0fa99c595c9c24a34b78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea84d9b2e7b591e7f953f93be6762a48238e4bcde1f372a905ac55d9ad06838e63646b231c82dfdb2dc644fcb6367330535cdb02c28dcb4e704b9ec35c67acef
|
7
|
+
data.tar.gz: 45a7a78e40e059a99118869a3f9815bf91ea1167927daeb7a9a515ad5333d0402f5c33005569dd30d13ccee841a862f12553693e4cf80e26bdd957942f8b70e8
|
data/README.md
CHANGED
@@ -35,6 +35,37 @@ http_range.first_inclusive # => true
|
|
35
35
|
http_range.last_inclusive # => false
|
36
36
|
```
|
37
37
|
|
38
|
+
Or, in a Rack app:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# Range: id a..z[; max=100
|
42
|
+
# found in env['HTTP_RANGE']
|
43
|
+
app = Rack::Builder.app do
|
44
|
+
use HTTPRange::Middleware::AcceptRanges
|
45
|
+
|
46
|
+
run lambda do |env|
|
47
|
+
|
48
|
+
env['rack.range.attribute'] # => 'id'
|
49
|
+
env['rack.range.first'] # => 'a'
|
50
|
+
env['rack.range.last'] # => 'z'
|
51
|
+
env['rack.range.first_inclusive'] # => true
|
52
|
+
env['rack.range.last_inclusive'] # => false
|
53
|
+
env['rack.range.order'] # => nil
|
54
|
+
env['rack.range.max'] # => '100'
|
55
|
+
|
56
|
+
[200, {}, "Body"]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
## Inspiration
|
62
|
+
|
63
|
+
Inspired by Heroku's [Interagent HTTP API Design][2] as well as brandur's
|
64
|
+
[HTTPAccept][3] library.
|
65
|
+
|
66
|
+
[2]: https://github.com/interagent/http-api-design
|
67
|
+
[3]: https://github.com/brandur/http_accept
|
68
|
+
|
38
69
|
## Contributing
|
39
70
|
|
40
71
|
1. Fork it ( https://github.com/[my-github-username]/http_range/fork )
|
data/SPEC.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Range HTTP Header Spec for HTTP APIs
|
2
|
+
|
3
|
+
Largely from [Heroku's Range header documentation][1].
|
4
|
+
|
5
|
+
[1]: https://devcenter.heroku.com/articles/platform-api-reference#ranges
|
6
|
+
|
7
|
+
```
|
8
|
+
Range: <attr> [<]>]<first>..<last>[<[>][; order=<asc|desc>][; max=<int>]
|
9
|
+
```
|
10
|
+
|
11
|
+
The `[` and `]` exclusion operators are [described thusly][2], assuming a range
|
12
|
+
from `a` to `z`:
|
13
|
+
|
14
|
+
[2]: https://github.com/interagent/http-api-design/issues/36#issuecomment-48226357
|
15
|
+
|
16
|
+
* `a..z` to get everything
|
17
|
+
* `a..` to get the default size worth of results greater than or equal to a
|
18
|
+
* `..z` to get the default size worth of results less than or equal to z
|
19
|
+
* `]a..` results greater than a (not greater than or equal as above)
|
20
|
+
* `..z[` results less than z (not less than or equal as above)
|
21
|
+
|
22
|
+
Intentional deviations from Heroku's design:
|
23
|
+
|
24
|
+
* Separate `order` and `max` into separate parameters delimited by `;` to avoid
|
25
|
+
two needless rounds of parsing (`;` and `,`)
|
data/http_range.gemspec
CHANGED
@@ -18,7 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
|
22
|
-
spec.add_development_dependency '
|
23
|
-
spec.add_development_dependency '
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
spec.add_development_dependency 'rack-test', '~> 0.6.2'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.1'
|
24
26
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class HTTPRange
|
4
|
+
module Middleware
|
5
|
+
|
6
|
+
# Rack Middleware for parsing `Range` headers. See [SPEC][1] for
|
7
|
+
# more details.
|
8
|
+
#
|
9
|
+
# [1]: https://github.com/h3h/http_range/blob/master/SPEC.md
|
10
|
+
#
|
11
|
+
# Request Header Format:
|
12
|
+
#
|
13
|
+
# Range: <attr> <first>..<last>[; order=<asc|desc>][; max=<int>]
|
14
|
+
#
|
15
|
+
# Examples:
|
16
|
+
#
|
17
|
+
# Range: id 29f99177-36e9-466c-baef-f855e1ab731e..6c714be1-d901-4c40-adb3-22c9a8cda950; max=100
|
18
|
+
# Range: created_at 2014-09-18T16:30:38Z..2014-09-18T17:30:33Z; order=desc
|
19
|
+
#
|
20
|
+
# Output:
|
21
|
+
#
|
22
|
+
# `env` keys exposed from the parsed content of the requested Range header:
|
23
|
+
#
|
24
|
+
# - env['rack.range.attribute']: attribute name
|
25
|
+
# - env['rack.range.first']: first value in the range
|
26
|
+
# - env['rack.range.last']: last value in the range
|
27
|
+
# - env['rack.range.first_inclusive']: whether the first value is inclusive
|
28
|
+
# - env['rack.range.last_inclusive']: whether the last value is inclusive
|
29
|
+
# - env['rack.range.order']: order param
|
30
|
+
# - env['rack.range.max']: max param
|
31
|
+
#
|
32
|
+
# Usage:
|
33
|
+
#
|
34
|
+
# use HTTPRange::Middleware::AcceptRanges
|
35
|
+
#
|
36
|
+
class AcceptRanges
|
37
|
+
def initialize(app)
|
38
|
+
@app = app
|
39
|
+
end
|
40
|
+
|
41
|
+
def call(env)
|
42
|
+
range_header = env['HTTP_RANGE']
|
43
|
+
if range_header && range_header.length > 0
|
44
|
+
http_range = HTTPRange.parse(range_header)
|
45
|
+
|
46
|
+
set_env_fields(
|
47
|
+
env: env,
|
48
|
+
attribute: http_range.attribute,
|
49
|
+
first: http_range.first,
|
50
|
+
last: http_range.last,
|
51
|
+
first_inclusive: http_range.first_inclusive,
|
52
|
+
last_inclusive: http_range.last_inclusive,
|
53
|
+
order: http_range.order,
|
54
|
+
max: http_range.max,
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
@app.call(env)
|
59
|
+
rescue HTTPRange::MalformedRangeHeaderError => ex
|
60
|
+
[400, {'Content-Type' => 'application/json'}, [{errors: [ex.message]}.to_json]]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Expose fields in the given env hash for the rest of the application.
|
66
|
+
#
|
67
|
+
# @param env [Hash]
|
68
|
+
# @param **fields [Hash]
|
69
|
+
#
|
70
|
+
# @return [nil]
|
71
|
+
#
|
72
|
+
def set_env_fields(env:, **fields)
|
73
|
+
fields.each do |field, value|
|
74
|
+
env["rack.range.#{field}"] = value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/lib/http_range/parser.rb
CHANGED
data/lib/http_range/version.rb
CHANGED
data/lib/http_range.rb
CHANGED
data/spec/http_range_spec.rb
CHANGED
@@ -6,7 +6,7 @@ RSpec.describe HTTPRange do
|
|
6
6
|
subject { described_class.parse(header) }
|
7
7
|
|
8
8
|
context "for a correctly formatted Range header" do
|
9
|
-
let(:header) {
|
9
|
+
let(:header) { 'Range: id 29f99177-36e9-466c-baef-f855e1ab731e..6c714be1-d901-4c40-adb3-22c9a8cda950[; max=100' }
|
10
10
|
|
11
11
|
it "returns an instance of the class" do
|
12
12
|
expect(subject).to be_an(HTTPRange)
|
@@ -62,5 +62,11 @@ RSpec.describe HTTPRange do
|
|
62
62
|
|
63
63
|
it { expect { subject }.to raise_error(HTTPRange::MalformedRangeHeaderError) }
|
64
64
|
end
|
65
|
+
|
66
|
+
context "for a nil Range header" do
|
67
|
+
let(:header) { nil }
|
68
|
+
|
69
|
+
it { expect { subject }.to raise_error(HTTPRange::MalformedRangeHeaderError) }
|
70
|
+
end
|
65
71
|
end
|
66
72
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'http_range'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe HTTPRange::Middleware::AcceptRanges do
|
5
|
+
let(:app) { ->(env) { [200, env, 'body'] } }
|
6
|
+
let(:middleware) { HTTPRange::Middleware::AcceptRanges.new(app) }
|
7
|
+
|
8
|
+
context "without a Range header" do
|
9
|
+
let(:headers) { {} }
|
10
|
+
|
11
|
+
before { @response = call_with_headers(middleware, headers) }
|
12
|
+
|
13
|
+
it { expect(@response.status).to eq 200 }
|
14
|
+
it { expect(@response['rack.range.attribute']).to be_nil }
|
15
|
+
it { expect(@response['rack.range.first']).to be_nil }
|
16
|
+
it { expect(@response['rack.range.last']).to be_nil }
|
17
|
+
it { expect(@response['rack.range.first_inclusive']).to be_nil }
|
18
|
+
it { expect(@response['rack.range.last_inclusive']).to be_nil }
|
19
|
+
it { expect(@response['rack.range.order']).to be_nil }
|
20
|
+
it { expect(@response['rack.range.max']).to be_nil }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with a valid Range header" do
|
24
|
+
let(:headers) { {'HTTP_RANGE' => 'Range: id a..z[; max=100'} }
|
25
|
+
|
26
|
+
before { @response = call_with_headers(middleware, headers) }
|
27
|
+
|
28
|
+
it { expect(@response.status).to eq 200 }
|
29
|
+
it { expect(@response['rack.range.attribute']).to eq('id') }
|
30
|
+
it { expect(@response['rack.range.first']).to eq('a') }
|
31
|
+
it { expect(@response['rack.range.last']).to eq('z') }
|
32
|
+
it { expect(@response['rack.range.first_inclusive']).to eq(true) }
|
33
|
+
it { expect(@response['rack.range.last_inclusive']).to eq(false) }
|
34
|
+
it { expect(@response['rack.range.order']).to be_nil }
|
35
|
+
it { expect(@response['rack.range.max']).to eq('100') }
|
36
|
+
end
|
37
|
+
|
38
|
+
def call_with_headers(middleware, headers)
|
39
|
+
Rack::MockRequest.new(middleware).get('/', headers)
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_range
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Fults
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack-test
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.2
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.6.2
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,11 +78,14 @@ files:
|
|
64
78
|
- LICENSE.txt
|
65
79
|
- README.md
|
66
80
|
- Rakefile
|
81
|
+
- SPEC.md
|
67
82
|
- http_range.gemspec
|
68
83
|
- lib/http_range.rb
|
84
|
+
- lib/http_range/middleware/accept_ranges.rb
|
69
85
|
- lib/http_range/parser.rb
|
70
86
|
- lib/http_range/version.rb
|
71
87
|
- spec/http_range_spec.rb
|
88
|
+
- spec/middleware/accept_ranges_spec.rb
|
72
89
|
- spec/spec_helper.rb
|
73
90
|
homepage: ''
|
74
91
|
licenses:
|
@@ -96,4 +113,5 @@ specification_version: 4
|
|
96
113
|
summary: Assists in parsing HTTP Range headers.
|
97
114
|
test_files:
|
98
115
|
- spec/http_range_spec.rb
|
116
|
+
- spec/middleware/accept_ranges_spec.rb
|
99
117
|
- spec/spec_helper.rb
|