http_content_type 0.0.1 → 0.0.2
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/CHANGELOG.md +16 -3
- data/lib/http_content_type.rb +3 -0
- data/lib/http_content_type/checker.rb +120 -37
- data/lib/http_content_type/version.rb +1 -1
- data/spec/lib/http_content_type/checker_spec.rb +165 -0
- metadata +5 -4
- data/spec/http_content_type/checker_spec.rb +0 -86
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 423abcfd36ea5de5ddb56ef406aa65d20c51a2e3
|
4
|
+
data.tar.gz: 41ce1d5652424ebd802464a109a42e8e9e463fbd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a35dbb8bc55e95a17683f25167798a9090b81cc58c63aab0c8b139628e0ad0a8e71ce939d742a81779ce9ee2a750b54dffa7a28bbb10ec1aabc1d1dfb358fa29
|
7
|
+
data.tar.gz: 0921e556085c0d2428678114eab56c93cdcad58257649ae19efebf72b87e0ca1045ac18fa235d44bc0377aea2dafb44bcd02ca4e22774a52a4b53a04f1f30fbf
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
## Master
|
2
2
|
|
3
|
-
|
3
|
+
No changes.
|
4
|
+
|
5
|
+
### 0.0.2 - 5 September, 2013 - ([@rymai][])
|
6
|
+
|
7
|
+
- Handle redirections & url with query params.
|
8
|
+
- Allow to pass options to `HttpContentType::Checker#initialize`.
|
9
|
+
- New `:timeout` option to customize the read timeout for the `HEAD` request that checks the `Content-Type` header.
|
10
|
+
- New `:expected_content_type` option to hardcode the expected `Content-Type`.
|
11
|
+
- New `#error?` method to check if the request on the asset failed for any reason.
|
12
|
+
- Default timeout is now 5 seconds (instead of 3).
|
13
|
+
- Documentation added for all public methods using Yard.
|
14
|
+
|
15
|
+
### 0.0.1 - 4 September, 2013 - ([@rymai][])
|
16
|
+
|
17
|
+
- Initial release.
|
4
18
|
|
5
|
-
- Initial release. ([@rymai][])
|
6
19
|
<!--- The following link definition list is generated by PimpMyChangelog --->
|
7
|
-
[@rymai]: https://github.com/rymai
|
20
|
+
[@rymai]: https://github.com/rymai
|
data/lib/http_content_type.rb
CHANGED
@@ -3,66 +3,149 @@ require 'net/http'
|
|
3
3
|
module HttpContentType
|
4
4
|
|
5
5
|
class Checker
|
6
|
-
|
7
|
-
FILE_NOT_FOUND_RESPONSE = { 'found' => false, 'content-type' => 'unknown' }
|
6
|
+
class TooManyRedirections < Exception; end
|
8
7
|
|
9
|
-
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
timeout: 5
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_accessor :expected_content_type, :options, :last_response
|
13
|
+
|
14
|
+
# Creates a `HttpContentType::Checker` object given an asset URL and
|
15
|
+
# options.
|
16
|
+
#
|
17
|
+
# @param asset_url [String] the asset URL
|
18
|
+
# @param opts [Hash] misc options
|
19
|
+
# @option opts [String] :timeout The read timeout for the `HEAD` request.
|
20
|
+
# @option opts [String] :expected_content_type The expected `Content-Type`
|
21
|
+
# for the given `asset_url`.
|
22
|
+
# @return [HttpContentType::Checker] the `HttpContentType::Checker` object
|
23
|
+
def initialize(asset_url, opts = {})
|
10
24
|
@asset_url = asset_url
|
25
|
+
@expected_content_type = opts.delete(:expected_content_type)
|
26
|
+
@options = DEFAULT_OPTIONS.merge(opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true if there was an error requesting the asset. Most common
|
30
|
+
# errors are `HTTPClientError`, `HTTPServerError`, `HTTPUnknownResponse`,
|
31
|
+
# `HttpContentType::TooManyRedirections`. Note that any other (less common)
|
32
|
+
# exceptions are catched as well.
|
33
|
+
#
|
34
|
+
# @return [Boolean] whether or not there was an error while requesting the
|
35
|
+
# asset.
|
36
|
+
def error?
|
37
|
+
!_head[:error].nil?
|
11
38
|
end
|
12
39
|
|
40
|
+
# Returns true if the asset was found (i.e. request returned an
|
41
|
+
# `Net::HTTPSuccess` response).
|
42
|
+
#
|
43
|
+
# Note: You should always check for `#error?` before checking for `#found?`
|
44
|
+
# to be sure you don't assume an asset doesn't exist when the request
|
45
|
+
# actually errored!
|
46
|
+
#
|
47
|
+
# @return [Boolean] whether or not the asset exists
|
13
48
|
def found?
|
14
|
-
_head[
|
49
|
+
_head[:found]
|
15
50
|
end
|
16
51
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
52
|
+
# Returns true if the asset's `Content-Type` is valid according to its
|
53
|
+
# extension.
|
54
|
+
#
|
55
|
+
# Note: This always returns true if `#error?` or `#found?` return true so
|
56
|
+
# be sure to check for `#found?` before checking for
|
57
|
+
# `#valid_content_type?`!
|
58
|
+
#
|
59
|
+
# @return [Boolean] whether or not the asset exists
|
60
|
+
def valid_content_type?
|
61
|
+
error? || !found? || content_type == expected_content_type
|
28
62
|
end
|
29
63
|
|
64
|
+
# Returns the `Content-Type` for the actually requested URL.
|
65
|
+
#
|
66
|
+
# Note: If the original URL included query parameters or redirected on
|
67
|
+
# another URL, the `Content-Type` is the one for the actually requested URL
|
68
|
+
# without the query parameters.
|
69
|
+
#
|
70
|
+
# @return [String] the `Content-Type` for the actual asset URL.
|
71
|
+
# @see #expected_content_type
|
30
72
|
def content_type
|
31
|
-
_head[
|
73
|
+
_head[:content_type]
|
32
74
|
end
|
33
75
|
|
34
|
-
|
35
|
-
|
76
|
+
# Returns the expected `Content-Type` for the actually requested URL, based
|
77
|
+
# on its extension or the `:expected_content_type` option passed when
|
78
|
+
# instantiating the `HttpContentType::Checker` object.
|
79
|
+
#
|
80
|
+
# @return [String] the expected `Content-Type` for the actual asset URL.
|
81
|
+
# @see #content_type
|
82
|
+
def expected_content_type
|
83
|
+
@expected_content_type ||= begin
|
84
|
+
case File.extname(_head[:uri].path).sub(/^\./, '')
|
85
|
+
when 'mp4', 'm4v', 'mov'
|
86
|
+
'video/mp4'
|
87
|
+
when 'webm'
|
88
|
+
'video/webm'
|
89
|
+
when 'ogv', 'ogg'
|
90
|
+
'video/ogg'
|
91
|
+
else
|
92
|
+
'unknown'
|
93
|
+
end
|
94
|
+
end
|
36
95
|
end
|
37
96
|
|
38
97
|
private
|
39
98
|
|
40
|
-
def
|
41
|
-
@
|
99
|
+
def _head
|
100
|
+
@head ||= _fetch(@asset_url)
|
42
101
|
end
|
43
102
|
|
44
|
-
def
|
45
|
-
@
|
103
|
+
def _connection_options(uri)
|
104
|
+
@_connection_options ||= { use_ssl: uri.scheme == 'https', read_timeout: options[:timeout] }
|
46
105
|
end
|
47
106
|
|
48
|
-
def
|
49
|
-
|
50
|
-
response = Net::HTTP.start(_clean_uri.host, _clean_uri.port, _head_options) do |http|
|
51
|
-
http.head(_clean_uri.path)
|
52
|
-
end
|
107
|
+
def _fetch(url, limit = 10)
|
108
|
+
raise TooManyRedirections if limit == 0
|
53
109
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
else
|
60
|
-
UNKNOWN_CONTENT_TYPE_RESPONSE
|
61
|
-
end
|
110
|
+
uri ||= URI.parse(URI.escape(url))
|
111
|
+
@last_response = Net::HTTP.start(uri.host, uri.port, _connection_options(uri)) do |http|
|
112
|
+
req = Net::HTTP::Head.new(uri.request_uri)
|
113
|
+
http.request(req)
|
114
|
+
end
|
62
115
|
|
63
|
-
|
64
|
-
|
116
|
+
case @last_response
|
117
|
+
when Net::HTTPSuccess
|
118
|
+
_found_response(uri, @last_response)
|
119
|
+
when Net::HTTPRedirection
|
120
|
+
_fetch(@last_response['location'], limit - 1)
|
121
|
+
when HTTPClientError
|
122
|
+
_not_found_response(uri, @last_response)
|
123
|
+
else
|
124
|
+
_errored_response(uri, @last_response)
|
65
125
|
end
|
126
|
+
|
127
|
+
rescue => ex
|
128
|
+
puts "Exception from HttpContentType::Checker#_fetch('#{uri}', #{limit}):"
|
129
|
+
puts ex
|
130
|
+
_errored_response(@last_response, error: ex)
|
131
|
+
end
|
132
|
+
|
133
|
+
def _found_response(uri, http_response)
|
134
|
+
_base_response(uri).merge(found: true, error: nil, content_type: http_response['content-type'])
|
135
|
+
end
|
136
|
+
|
137
|
+
def _not_found_response(uri, http_response)
|
138
|
+
_base_response(uri).merge(found: false, error: nil, content_type: 'unknown')
|
139
|
+
end
|
140
|
+
|
141
|
+
def _errored_response(uri, http_response, opts = {})
|
142
|
+
error = opts[:error] || "#{http_response.code}: #{http_response.message}"
|
143
|
+
|
144
|
+
_base_response(uri).merge(found: false, error: error, content_type: 'unknown')
|
145
|
+
end
|
146
|
+
|
147
|
+
def _base_response(uri)
|
148
|
+
{ uri: uri }
|
66
149
|
end
|
67
150
|
|
68
151
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'http_content_type/checker'
|
4
|
+
|
5
|
+
describe HttpContentType::Checker do
|
6
|
+
let(:checker) { described_class.new('http://foo.com/bar.mp4') }
|
7
|
+
subject { checker }
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
it 'has default options' do
|
11
|
+
expect(checker.options[:timeout]).to eq 5
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ':timeout options' do
|
15
|
+
it 'is customizable' do
|
16
|
+
checker = described_class.new('http://foo.com/bar.mp4', timeout: 10)
|
17
|
+
|
18
|
+
expect(checker.options[:timeout]).to eq 10
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ':expected_content_type options' do
|
23
|
+
it 'is customizable' do
|
24
|
+
checker = described_class.new('http://foo.com/dynamic_asset.php', expected_content_type: 'video/mp4')
|
25
|
+
|
26
|
+
expect(checker.expected_content_type).to eq 'video/mp4'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#error?' do
|
32
|
+
context 'asset do not return an error' do
|
33
|
+
before { checker.stub(:_head).and_return({ found: false, error: nil }) }
|
34
|
+
|
35
|
+
it 'return false' do
|
36
|
+
checker.should_not be_error
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'asset returns an error' do
|
41
|
+
before { checker.stub(:_head).and_return({ found: false, error: true }) }
|
42
|
+
|
43
|
+
it 'return true' do
|
44
|
+
checker.should be_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#found?' do
|
50
|
+
context 'asset is not found' do
|
51
|
+
before { checker.stub(:_head).and_return({ found: false }) }
|
52
|
+
|
53
|
+
it 'return false' do
|
54
|
+
checker.should_not be_found
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'asset is found' do
|
59
|
+
before { checker.stub(:_head).and_return({ found: true, content_type: 'video/mp4' }) }
|
60
|
+
|
61
|
+
it 'return true' do
|
62
|
+
checker.should be_found
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#expected_content_type' do
|
68
|
+
context '.mp4 video' do
|
69
|
+
subject { described_class.new('http://foo.com/bar.mp4') }
|
70
|
+
its(:expected_content_type) { eq 'video/mp4' }
|
71
|
+
end
|
72
|
+
|
73
|
+
context '.mp4 video with query params' do
|
74
|
+
subject { described_class.new('http://foo.com/bar.mp4?foo=bar') }
|
75
|
+
its(:expected_content_type) { eq 'video/mp4' }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'given a hardcoded content-type' do
|
79
|
+
subject { described_class.new('http://foo.com/dynamic_asset.php', expected_content_type: 'video/mp4') }
|
80
|
+
its(:expected_content_type) { eq 'video/mp4' }
|
81
|
+
end
|
82
|
+
|
83
|
+
context '.m4v video' do
|
84
|
+
subject { described_class.new('http://foo.com/bar.m4v') }
|
85
|
+
its(:expected_content_type) { eq 'video/mp4' }
|
86
|
+
end
|
87
|
+
|
88
|
+
context '.mov video' do
|
89
|
+
subject { described_class.new('http://foo.com/bar.mov') }
|
90
|
+
its(:expected_content_type) { eq 'video/mp4' }
|
91
|
+
end
|
92
|
+
|
93
|
+
context '.webm video' do
|
94
|
+
subject { described_class.new('http://foo.com/bar.webm') }
|
95
|
+
its(:expected_content_type) { eq 'video/webm' }
|
96
|
+
end
|
97
|
+
|
98
|
+
context '.ogg video' do
|
99
|
+
subject { described_class.new('http://foo.com/bar.ogg') }
|
100
|
+
its(:expected_content_type) { eq 'video/ogg' }
|
101
|
+
end
|
102
|
+
|
103
|
+
context '.ogv video' do
|
104
|
+
subject { described_class.new('http://foo.com/bar.ogv') }
|
105
|
+
its(:expected_content_type) { eq 'video/ogg' }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#content_type' do
|
110
|
+
context 'fake asset has valid content type' do
|
111
|
+
before { checker.stub(:_head).and_return({ found: true, content_type: 'video/mp4' }) }
|
112
|
+
|
113
|
+
it 'returns the actual content type' do
|
114
|
+
expect(checker.content_type).to eq 'video/mp4'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'real asset has valid content type' do
|
119
|
+
let(:checker) { described_class.new('http://player.vimeo.com/external/51920681.hd.mp4?s=70273279a571e027c54032e70db61253') }
|
120
|
+
|
121
|
+
it 'returns the actual content type' do
|
122
|
+
expect(checker.content_type).to eq 'video/mp4'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '#valid_content_type?' do
|
128
|
+
context 'asset cannot be found and returns an error' do
|
129
|
+
before { checker.stub(:_head).and_return({ uri: URI('http://foo.com/bar.mp4'), found: false, error: true }) }
|
130
|
+
it { be_valid_content_type }
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'asset cannot be found and do not return an error', :focus do
|
134
|
+
before { checker.stub(:_head).and_return({ uri: URI('http://foo.com/bar.mp4'), found: false, error: nil }) }
|
135
|
+
it { be_valid_content_type }
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'asset is found without an error with valid content type', :focus do
|
139
|
+
before { checker.stub(:_head).and_return({ uri: URI('http://foo.com/bar.mp4'), found: true, error: nil, content_type: 'video/mp4' }) }
|
140
|
+
it { be_valid_content_type }
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'asset is found without an error with invalid content type', :focus do
|
144
|
+
before { checker.stub(:_head).and_return({ uri: URI('http://foo.com/bar.mov'), found: true, error: nil, content_type: 'video/mov' }) }
|
145
|
+
it { be_valid_content_type }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#_fetch' do
|
150
|
+
context 'too many redirections' do
|
151
|
+
let(:response) do
|
152
|
+
Net::HTTPRedirection.new('1.1', 302, '').tap do |res|
|
153
|
+
res['content-type'] = 'foo/bar'
|
154
|
+
res['location'] = 'http://domain.com/video.mp4'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
before { Net::HTTP.any_instance.stub(:request).and_return(response) }
|
158
|
+
|
159
|
+
it 'raise a TooManyRedirections exception' do
|
160
|
+
expect { checker.send(:_fetch, 'http://domain.com/video.mp4') }.to raise_error(HttpContentType::TooManyRedirections)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_content_type
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémy Coutable
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,7 +52,7 @@ files:
|
|
52
52
|
- CHANGELOG.md
|
53
53
|
- LICENSE.md
|
54
54
|
- README.md
|
55
|
-
- spec/http_content_type/checker_spec.rb
|
55
|
+
- spec/lib/http_content_type/checker_spec.rb
|
56
56
|
- spec/spec_helper.rb
|
57
57
|
homepage: http://rubygems.org/gems/http_content_type
|
58
58
|
licenses:
|
@@ -79,5 +79,6 @@ signing_key:
|
|
79
79
|
specification_version: 4
|
80
80
|
summary: Check the Content-Type of any HTTP-accessible asset.
|
81
81
|
test_files:
|
82
|
-
- spec/http_content_type/checker_spec.rb
|
82
|
+
- spec/lib/http_content_type/checker_spec.rb
|
83
83
|
- spec/spec_helper.rb
|
84
|
+
has_rdoc:
|
@@ -1,86 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'http_content_type/checker'
|
4
|
-
|
5
|
-
describe HttpContentType::Checker do
|
6
|
-
let(:checker) { described_class.new('http://foo.com/bar.mp4') }
|
7
|
-
|
8
|
-
describe '#found?' do
|
9
|
-
context 'asset is not found' do
|
10
|
-
before { checker.stub(:_head).and_return({ 'found' => false }) }
|
11
|
-
|
12
|
-
it 'return false' do
|
13
|
-
checker.should_not be_found
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context 'asset is found' do
|
18
|
-
before { checker.stub(:_head).and_return({ 'found' => true, 'content-type' => 'video/mp4' }) }
|
19
|
-
|
20
|
-
it 'return true' do
|
21
|
-
checker.should be_found
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe '#expected_content_type' do
|
27
|
-
context '.mp4 video' do
|
28
|
-
subject { described_class.new('http://foo.com/bar.mp4') }
|
29
|
-
its(:expected_content_type) { eq 'video/mp4' }
|
30
|
-
end
|
31
|
-
|
32
|
-
context '.m4v video' do
|
33
|
-
subject { described_class.new('http://foo.com/bar.m4v') }
|
34
|
-
its(:expected_content_type) { eq 'video/mp4' }
|
35
|
-
end
|
36
|
-
|
37
|
-
context '.mov video' do
|
38
|
-
subject { described_class.new('http://foo.com/bar.mov') }
|
39
|
-
its(:expected_content_type) { eq 'video/mp4' }
|
40
|
-
end
|
41
|
-
|
42
|
-
context '.webm video' do
|
43
|
-
subject { described_class.new('http://foo.com/bar.webm') }
|
44
|
-
its(:expected_content_type) { eq 'video/webm' }
|
45
|
-
end
|
46
|
-
|
47
|
-
context '.ogg video' do
|
48
|
-
subject { described_class.new('http://foo.com/bar.ogg') }
|
49
|
-
its(:expected_content_type) { eq 'video/ogg' }
|
50
|
-
end
|
51
|
-
|
52
|
-
context '.ogv video' do
|
53
|
-
subject { described_class.new('http://foo.com/bar.ogv') }
|
54
|
-
its(:expected_content_type) { eq 'video/ogg' }
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe '#content_type' do
|
59
|
-
context 'asset has valid content type' do
|
60
|
-
before { checker.stub(:_head).and_return({ 'found' => true, 'content-type' => 'video/mp4' }) }
|
61
|
-
|
62
|
-
it 'returns the actual content type' do
|
63
|
-
expect(checker.content_type).to eq 'video/mp4'
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
describe '#valid_content_type?' do
|
69
|
-
context 'asset has valid content type' do
|
70
|
-
before { checker.stub(:_head).and_return({ 'found' => true, 'content-type' => 'video/mp4' }) }
|
71
|
-
|
72
|
-
it 'returns true' do
|
73
|
-
checker.should be_valid_content_type
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
context 'asset has invalid content type' do
|
78
|
-
before { checker.stub(:_head).and_return({ 'found' => true, 'content-type' => 'video/mov' }) }
|
79
|
-
|
80
|
-
it 'returns false' do
|
81
|
-
checker.should_not be_valid_content_type
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|