micky 0.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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +139 -0
- data/Rakefile +1 -0
- data/lib/micky/request.rb +90 -0
- data/lib/micky/response.rb +38 -0
- data/lib/micky/uri.rb +12 -0
- data/lib/micky/version.rb +3 -0
- data/lib/micky.rb +37 -0
- data/micky.gemspec +27 -0
- metadata +85 -0
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
data/Gemfile
ADDED
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
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: []
|