http_content_type 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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