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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f3bdaf6bde10e228f63867b5994cc3c9e1d6c514
4
- data.tar.gz: 99274fe10583a1cbe8d04dd260874b29507ed8ec
3
+ metadata.gz: 423abcfd36ea5de5ddb56ef406aa65d20c51a2e3
4
+ data.tar.gz: 41ce1d5652424ebd802464a109a42e8e9e463fbd
5
5
  SHA512:
6
- metadata.gz: 151838a3c37cb555a9de9aecfb3aa8fa774f75bd1ab30b67c295dbf2aa90ce66488c59120d34c138c99c5e94ddee7f7ad7627e6c915f3ee627a0d13a2e05581c
7
- data.tar.gz: 7155b8a36f44e6d8e3000f7743573cd6acae8961acf2b034801aa4ce8391b4f372e3016cdbd7fff1bb87e9a0eaaffde51323b1114fe3c61650c5417091d5aa45
6
+ metadata.gz: a35dbb8bc55e95a17683f25167798a9090b81cc58c63aab0c8b139628e0ad0a8e71ce939d742a81779ce9ee2a750b54dffa7a28bbb10ec1aabc1d1dfb358fa29
7
+ data.tar.gz: 0921e556085c0d2428678114eab56c93cdcad58257649ae19efebf72b87e0ca1045ac18fa235d44bc0377aea2dafb44bcd02ca4e22774a52a4b53a04f1f30fbf
@@ -1,7 +1,20 @@
1
1
  ## Master
2
2
 
3
- ### 0.0.1 - 4 September, 2013
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
@@ -1,2 +1,5 @@
1
1
  require 'http_content_type/version'
2
2
  require 'http_content_type/checker'
3
+
4
+ module HttpContentType
5
+ end
@@ -3,66 +3,149 @@ require 'net/http'
3
3
  module HttpContentType
4
4
 
5
5
  class Checker
6
- UNKNOWN_CONTENT_TYPE_RESPONSE = { 'found' => true, 'content-type' => 'unknown' }
7
- FILE_NOT_FOUND_RESPONSE = { 'found' => false, 'content-type' => 'unknown' }
6
+ class TooManyRedirections < Exception; end
8
7
 
9
- def initialize(asset_url)
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['found']
49
+ _head[:found]
15
50
  end
16
51
 
17
- def expected_content_type
18
- @expected_content_type ||= case File.extname(@asset_url).sub(/^\./, '')
19
- when 'mp4', 'm4v', 'mov'
20
- 'video/mp4'
21
- when 'webm'
22
- 'video/webm'
23
- when 'ogv', 'ogg'
24
- 'video/ogg'
25
- else
26
- 'unknown'
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['content-type']
73
+ _head[:content_type]
32
74
  end
33
75
 
34
- def valid_content_type?
35
- content_type == expected_content_type
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 _clean_uri
41
- @_clean_uri ||= URI.parse(URI.escape(@asset_url))
99
+ def _head
100
+ @head ||= _fetch(@asset_url)
42
101
  end
43
102
 
44
- def _head_options
45
- @_head_options ||= { use_ssl: _clean_uri.scheme == 'https', read_timeout: 3 }
103
+ def _connection_options(uri)
104
+ @_connection_options ||= { use_ssl: uri.scheme == 'https', read_timeout: options[:timeout] }
46
105
  end
47
106
 
48
- def _head
49
- @response ||= begin
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
- case response
55
- when Net::HTTPSuccess, Net::HTTPRedirection
56
- { 'found' => true, 'content-type' => response['content-type'] }
57
- when Net::HTTPClientError
58
- FILE_NOT_FOUND_RESPONSE
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
- rescue
64
- UNKNOWN_CONTENT_TYPE_RESPONSE
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
@@ -1,3 +1,3 @@
1
1
  module HttpContentType
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  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.1
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-04 00:00:00.000000000 Z
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