http_range 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|