micky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4c3ca66fab6596cf504eb875cfdac174caf530c6
4
+ data.tar.gz: bbaf1adc0f7fdf7e2a1bba530872e0e70f3374d6
5
+ SHA512:
6
+ metadata.gz: da7fe99bed4c2819a06fa22f036a704ade75ae6e731cb712c455365f07818d52d2d11ef2bb7c33ba3c3550ebb39ee128861fc9554bf25223c46372cc3c269183
7
+ data.tar.gz: 2868f075661f8bb2b1a0722bb992088b71a86db44cdb8cb624b1e5b882668809ae18883d8256ae85871357d40476b573d3f218ae7c90eb8bccbd83268457c676
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Rafaël Blais Masson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Micky
2
+
3
+ Micky makes simple HTTP requests (`GET`/`HEAD`), follows redirects, handles
4
+ exceptions (invalid hosts/URIs, server errors, timeouts, redirect loops),
5
+ automatically parses responses (JSON, etc.), is very lightweight, and has no
6
+ dependency.
7
+
8
+ Micky is for those times you would have used
9
+ [`Net::HTTP`](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html‎)
10
+ or [`OpenURI`](http://ruby-doc.org/stdlib/libdoc/open-uri/rdoc/OpenURI.html),
11
+ but don’t want to bother handling all the sneaky things mentionned above, and
12
+ don’t want to add heavy dependencies to your app.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application’s Gemfile:
17
+
18
+ gem 'micky'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install micky
27
+
28
+ ## Usage
29
+
30
+ Micky provides two methods: `get` and `head`.
31
+
32
+ On successful requests, it will return a subclass of
33
+ [`Net::HTTPReponse`](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTPResponse.html).
34
+ For any error it might encounter during the request (invalid hosts/URIs,
35
+ server errors, timeouts, redirect loops), it will return `nil`.
36
+
37
+ ```ruby
38
+ response = Micky.get('http://google.com')
39
+ response.content_type # "text/html"
40
+ response.body # "<!doctype html><html ..."
41
+
42
+ response = Micky.get('http://invalidhost.foo')
43
+ response # nil
44
+ ```
45
+
46
+ ### Classic example
47
+
48
+ ```ruby
49
+ if Micky.head(params[:website_url])
50
+ # User provided a valid URL
51
+ url = URI(params[:website_url])
52
+ url.path = '/favicon.ico'
53
+
54
+ if favicon = Micky.get(url)
55
+ # Do whatever with the raw `favicon.data`, for whatever reason
56
+ else
57
+ # This site has no favicon, or a broken one, too bad
58
+ end
59
+ else
60
+ # Some error happened, display error message to user
61
+ end
62
+ ```
63
+
64
+ ### Automatically parse responses into Ruby objects
65
+
66
+ `Micky::Response#body` always returns the response as a string. To parse this
67
+ string into a Ruby object, use `Micky::Response#data`.
68
+
69
+ Responses with `Content-Type: application/json` are automatically parsed by
70
+ Ruby’s [`JSON`](http://ruby-doc.org/stdlib/libdoc/json/rdoc/JSON.html) library.
71
+
72
+ ```ruby
73
+ response = Micky.get('http://urls.api.twitter.com/1/urls/count.json?url=dropmeme.com')
74
+ response.content_type # 'application/json'
75
+
76
+ # plain string
77
+ response.body # '{"count":33,"url":"http://dropmeme.com/"}'
78
+
79
+ # proper hash
80
+ response.data # {"count"=>33, "url"=>"http://dropmeme.com/"}
81
+ ```
82
+
83
+ #### Add custom parsers
84
+
85
+ To add custom response parsers for specific content-types, insert lambdas in
86
+ the `Micky.parsers` hash.
87
+
88
+ For instance, to parse HTML documents with [Nokogiri](http://nokogiri.org):
89
+
90
+ ```ruby
91
+ Micky.parsers['text/html'] = -> (body) {
92
+ Nokogiri::HTML(body)
93
+ }
94
+ ```
95
+
96
+ Overwrite the default `application/json` parser to use
97
+ [Oj](http://github.com/ohler55/oj):
98
+
99
+ ```ruby
100
+ Micky.parsers['application/json'] = -> (body) {
101
+ begin
102
+ Oj.load(body)
103
+ rescue Oj::ParseError
104
+ end
105
+ }
106
+ ```
107
+
108
+ Parse images into [mini_magick](https://github.com/minimagick/minimagick)
109
+ instances:
110
+
111
+ ```ruby
112
+ parser = -> (body) {
113
+ begin
114
+ MiniMagick::Image.read(body)
115
+ rescue MiniMagick::Invalid
116
+ end
117
+ }
118
+
119
+ %w[image/png image/jpeg image/jpg image/gif].each do |type|
120
+ Micky.parsers[type] = parser
121
+ end
122
+ ```
123
+
124
+ ## TODO
125
+
126
+ - Add tests
127
+ - Better document configuration options in README
128
+
129
+ ## Contributing
130
+
131
+ 1. Fork it
132
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
133
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
134
+ 4. Push to the branch (`git push origin my-new-feature`)
135
+ 5. Create new Pull Request
136
+
137
+ ---
138
+
139
+ © 2013 [Rafaël Blais Masson](http://rafbm.com). Micky is released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,90 @@
1
+ require 'net/https'
2
+ require 'timeout'
3
+
4
+ module Micky
5
+ class Request
6
+ def initialize(opts = {})
7
+ # Options can be set per request and fallback to module-level defaults
8
+ [:max_redirects, :timeout, :skip_resolve, :resolve_timeout, :headers, :parsers].each do |name|
9
+ value = opts.has_key?(name) ? opts[name] : Micky.public_send(name)
10
+ instance_variable_set "@#{name}", value
11
+ end
12
+ end
13
+
14
+ def get(uri)
15
+ @request_class_name = 'Get'
16
+ request(uri)
17
+ end
18
+
19
+ def head(uri)
20
+ @request_class_name = 'Head'
21
+ request(uri)
22
+ end
23
+
24
+ private
25
+
26
+ def request(uri)
27
+ @uri = uri
28
+ request_with_redirect_handling(0)
29
+ end
30
+
31
+ def request_with_redirect_handling(redirect_count)
32
+ return log "Max redirects reached (#{@max_redirects})" if redirect_count >= @max_redirects
33
+
34
+ @uri = Micky::URI(@uri)
35
+
36
+ unless @skip_resolve == true
37
+ # Resolv is the only domain validity check that can be wrapped with Timeout.
38
+ # Net::HTTP and OpenURI use TCPSocket.open which isn’t timeoutable.
39
+ require 'resolv' unless defined? Resolv
40
+ begin
41
+ Timeout.timeout(@resolve_timeout) do
42
+ begin
43
+ Resolv::DNS.new.getaddress(@uri.host)
44
+ rescue Resolv::ResolvError
45
+ log 'Domain resolution error'
46
+ return nil
47
+ end
48
+ end
49
+ rescue Timeout::Error
50
+ log 'Domain resolution timeout'
51
+ return nil
52
+ end
53
+ end
54
+
55
+ # Connection
56
+ http = Net::HTTP.new(@uri.host, @uri.port)
57
+ http.use_ssl = @uri.scheme == 'https'
58
+
59
+ http.open_timeout = @timeout
60
+ http.read_timeout = @timeout
61
+ http.ssl_timeout = @timeout
62
+
63
+ # Request
64
+ request = Net::HTTP.const_get(@request_class_name).new(@uri)
65
+ @headers.each { |k,v| request[k] = v }
66
+
67
+ response = http.request(request)
68
+
69
+ case response
70
+ when Net::HTTPSuccess
71
+ Response.new(response)
72
+ when Net::HTTPRedirection
73
+ log "Redirect to #{response['Location']}"
74
+ @uri = response['Location']
75
+ request_with_redirect_handling(redirect_count + 1)
76
+ else
77
+ log response
78
+ nil
79
+ end
80
+ rescue Timeout::Error, ::URI::InvalidURIError, OpenSSL::SSL::SSLError, SystemCallError, SocketError => e
81
+ log e
82
+ nil
83
+ end
84
+
85
+ def log(message)
86
+ message = "#{message.class}: #{message.message}" if message.is_a? Exception
87
+ warn "Micky.#{@request_class_name.downcase}('#{@uri}'): #{message}"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,38 @@
1
+ require 'delegate'
2
+
3
+ module Micky
4
+ # Delegates to a Net::HTTPResponse instance
5
+ class Response < SimpleDelegator
6
+ def data
7
+ @data ||= begin
8
+ if body and parser = Micky.parsers[content_type]
9
+ parser.call(body)
10
+ else
11
+ body
12
+ end
13
+ end
14
+ end
15
+
16
+ def data_uri
17
+ @data_uri ||= begin
18
+ if body
19
+ require 'base64' unless defined? Base64
20
+ "data:#{content_type};base64,#{Base64.encode64(body)}"
21
+ end
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ body
27
+ end
28
+
29
+ def inspect
30
+ "#<Micky::Reponse #{super}>"
31
+ end
32
+
33
+ # Support for `awesome_print`
34
+ def ai(*args)
35
+ "#<Micky::Reponse #{super}>"
36
+ end
37
+ end
38
+ end
data/lib/micky/uri.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'delegate'
2
+ require 'uri'
3
+
4
+ module Micky
5
+ HTTP_URI_REGEX = /\Ahttps?:\/\//
6
+
7
+ def self.URI(uri)
8
+ uri = uri.to_s.strip
9
+ uri = "http://#{uri}" if uri !~ HTTP_URI_REGEX
10
+ ::URI.parse(uri) rescue ::URI.parse(::URI.encode(uri))
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Micky
2
+ VERSION = '0.1.0'
3
+ end
data/lib/micky.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'micky/version'
2
+
3
+ require 'micky/uri'
4
+ require 'micky/request'
5
+ require 'micky/response'
6
+
7
+ module Micky
8
+ class << self
9
+ attr_accessor :max_redirects
10
+ attr_accessor :timeout
11
+ attr_accessor :skip_resolve
12
+ attr_accessor :resolve_timeout
13
+ attr_accessor :headers
14
+ attr_accessor :parsers
15
+ end
16
+
17
+ # Reasonable defaults
18
+ @max_redirects = 10
19
+ @timeout = 5
20
+ @skip_resolve = false
21
+ @resolve_timeout = 2
22
+ @headers = {}
23
+ @parsers = {
24
+ 'application/json' => -> (body) {
25
+ require 'json' unless defined? JSON
26
+ JSON.parse(body) rescue nil
27
+ }
28
+ }
29
+
30
+ def self.get(uri, opts = {})
31
+ Request.new(opts).get(uri)
32
+ end
33
+
34
+ def self.head(uri, opts = {})
35
+ Request.new(opts).head(uri)
36
+ end
37
+ end
data/micky.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'micky/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'micky'
8
+ spec.version = Micky::VERSION
9
+ spec.authors = ['Rafaël Blais Masson']
10
+ spec.email = ['rafbmasson@gmail.com']
11
+ spec.description =
12
+ 'Micky makes simple HTTP requests (GET/HEAD), follows redirects, handles ' \
13
+ 'exceptions (invalid hosts/URIs, server errors, timeouts, redirect loops), ' \
14
+ 'automatically parses responses (JSON, etc.), is very lightweight, and has no ' \
15
+ 'dependency.'
16
+ spec.summary = 'Simple, worry-free HTTP client'
17
+ spec.homepage = 'http://github.com/rafBM/micky'
18
+ spec.license = 'MIT'
19
+
20
+ spec.files = `git ls-files`.split($/)
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'rake'
27
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: micky
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rafaël Blais Masson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Micky makes simple HTTP requests (GET/HEAD), follows redirects, handles
42
+ exceptions (invalid hosts/URIs, server errors, timeouts, redirect loops), automatically
43
+ parses responses (JSON, etc.), is very lightweight, and has no dependency.
44
+ email:
45
+ - rafbmasson@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - .gitignore
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - lib/micky.rb
56
+ - lib/micky/request.rb
57
+ - lib/micky/response.rb
58
+ - lib/micky/uri.rb
59
+ - lib/micky/version.rb
60
+ - micky.gemspec
61
+ homepage: http://github.com/rafBM/micky
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.0.3
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Simple, worry-free HTTP client
85
+ test_files: []