aitch 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.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +141 -0
- data/Rakefile +1 -0
- data/aitch.gemspec +28 -0
- data/lib/aitch.rb +47 -0
- data/lib/aitch/configuration.rb +48 -0
- data/lib/aitch/errors.rb +41 -0
- data/lib/aitch/redirect.rb +20 -0
- data/lib/aitch/request.rb +105 -0
- data/lib/aitch/response.rb +74 -0
- data/lib/aitch/response/body.rb +29 -0
- data/lib/aitch/response/errors.rb +39 -0
- data/lib/aitch/version.rb +3 -0
- data/lib/aitch/xml_parser.rb +7 -0
- data/spec/aitch/aitch_spec.rb +62 -0
- data/spec/aitch/configuration_spec.rb +33 -0
- data/spec/aitch/request_spec.rb +155 -0
- data/spec/aitch/response_spec.rb +180 -0
- data/spec/aitch/xml_parser_spec.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- metadata +188 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --order random --format documentation
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Nando Vieira
|
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,141 @@
|
|
1
|
+
# Aitch
|
2
|
+
|
3
|
+
[](https://travis-ci.org/fnando/aitch)
|
4
|
+
|
5
|
+
[](https://codeclimate.com/github/fnando/aitch/)
|
6
|
+
|
7
|
+
[](https://rubygems.org/gems/aitch)
|
8
|
+
|
9
|
+
A simple HTTP client.
|
10
|
+
|
11
|
+
Features:
|
12
|
+
|
13
|
+
* Supports Gzip|Deflate response
|
14
|
+
* Automatically parses JSON and XML responses
|
15
|
+
* Automatically follows redirect
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'aitch'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install aitch
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### Configuration
|
36
|
+
|
37
|
+
These are the default settings:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Aitch.configure do |config|
|
41
|
+
# Set request timeout.
|
42
|
+
config.timeout = 5
|
43
|
+
|
44
|
+
# Set default headers.
|
45
|
+
config.default_headers = {}
|
46
|
+
|
47
|
+
# Set follow redirect.
|
48
|
+
config.follow_redirect = true
|
49
|
+
|
50
|
+
# Set redirection limit.
|
51
|
+
config.redirect_limit = 5
|
52
|
+
|
53
|
+
# Set the user agent.
|
54
|
+
config.user_agent = "Aitch/0.0.1 (http://rubygems.org/gems/aitch)"
|
55
|
+
|
56
|
+
# Set the logger.
|
57
|
+
config.logger = nil
|
58
|
+
|
59
|
+
# Set the JSON parser.
|
60
|
+
config.json_parser = JSON
|
61
|
+
|
62
|
+
# Set the XML parser.
|
63
|
+
config.xml_parser = Aitch::XMLParser
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Requests
|
68
|
+
|
69
|
+
Performing requests:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
response = Aitch.get("http://example.org", params, headers)
|
73
|
+
Aitch.post("http://example.org", params, headers)
|
74
|
+
Aitch.put("http://example.org", params, headers)
|
75
|
+
Aitch.patch("http://example.org", params, headers)
|
76
|
+
Aitch.delete("http://example.org", params, headers)
|
77
|
+
Aitch.options("http://example.org", params, headers)
|
78
|
+
Aitch.trace("http://example.org", params, headers)
|
79
|
+
Aitch.head("http://example.org", params, headers)
|
80
|
+
```
|
81
|
+
|
82
|
+
### Response
|
83
|
+
|
84
|
+
The response object:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
response.html?
|
88
|
+
response.xml?
|
89
|
+
response.json?
|
90
|
+
response.content_type
|
91
|
+
response.headers
|
92
|
+
response.location
|
93
|
+
response.success? # status >= 200 && status <= 399
|
94
|
+
response.redirect? # status 3xx
|
95
|
+
response.error? # status 4xx or 5xx
|
96
|
+
response.error # response error
|
97
|
+
response.body # returned body
|
98
|
+
response.data # JSON or XML payload
|
99
|
+
```
|
100
|
+
|
101
|
+
### Following redirects
|
102
|
+
|
103
|
+
The configuration:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
Aitch.configure do |config|
|
107
|
+
config.follow_redirect = true
|
108
|
+
config.redirect_limit = 10
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
The request:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
Aitch.get("http://example.org")
|
116
|
+
```
|
117
|
+
|
118
|
+
If the redirect limit is exceeded, then the `Aitch::TooManyRedirectsError` exception
|
119
|
+
is raised.
|
120
|
+
|
121
|
+
### Basic auth
|
122
|
+
|
123
|
+
Setting basic auth credentials:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
Aitch.get("http://restrict.example.org/", {}, {}, user: "john", password: "test")
|
127
|
+
```
|
128
|
+
|
129
|
+
### Setting custom headers
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
Aitch.get("http://example.org", {}, {"User-Agent" => "MyBot/1.0.0"})
|
133
|
+
```
|
134
|
+
|
135
|
+
## Contributing
|
136
|
+
|
137
|
+
1. Fork it
|
138
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
139
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
140
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
141
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/aitch.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'aitch/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "aitch"
|
8
|
+
spec.version = Aitch::VERSION
|
9
|
+
spec.authors = ["Nando Vieira"]
|
10
|
+
spec.email = ["fnando.vieira@gmail.com"]
|
11
|
+
spec.description = "A simple HTTP client"
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = "http://rubygems.org/gems/aitch"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "pry-meta"
|
25
|
+
spec.add_development_dependency "test_notifier"
|
26
|
+
spec.add_development_dependency "fakeweb"
|
27
|
+
spec.add_development_dependency "nokogiri"
|
28
|
+
end
|
data/lib/aitch.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "net/https"
|
2
|
+
require "forwardable"
|
3
|
+
require "json"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
require "aitch/configuration"
|
7
|
+
require "aitch/errors"
|
8
|
+
require "aitch/request"
|
9
|
+
require "aitch/redirect"
|
10
|
+
require "aitch/response"
|
11
|
+
require "aitch/response/errors"
|
12
|
+
require "aitch/response/body"
|
13
|
+
require "aitch/xml_parser"
|
14
|
+
require "aitch/version"
|
15
|
+
|
16
|
+
module Aitch
|
17
|
+
extend self
|
18
|
+
|
19
|
+
def execute(method, url, args = {}, headers = {}, options = {})
|
20
|
+
Request.new(method, url, args, headers, options).perform
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute!(*args)
|
24
|
+
response = execute(*args)
|
25
|
+
raise response.error if response.error?
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
%w[
|
30
|
+
get
|
31
|
+
post
|
32
|
+
put
|
33
|
+
patch
|
34
|
+
delete
|
35
|
+
options
|
36
|
+
trace
|
37
|
+
head
|
38
|
+
].each do |method_name|
|
39
|
+
define_method(method_name) do |url, args = {}, headers = {}, options = {}|
|
40
|
+
execute(method_name, url, args, headers, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
define_method("#{method_name}!") do |url, args = {}, headers = {}, options = {}|
|
44
|
+
execute!(method_name, url, args, headers, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Aitch
|
2
|
+
class Configuration
|
3
|
+
# Set proxy.
|
4
|
+
attr_accessor :proxy
|
5
|
+
|
6
|
+
# Set request timeout.
|
7
|
+
attr_accessor :timeout
|
8
|
+
|
9
|
+
# Set default headers.
|
10
|
+
attr_accessor :default_headers
|
11
|
+
|
12
|
+
# Set follow redirect.
|
13
|
+
attr_accessor :follow_redirect
|
14
|
+
|
15
|
+
# Set redirection limit.
|
16
|
+
attr_accessor :redirect_limit
|
17
|
+
|
18
|
+
# Set the user agent.
|
19
|
+
attr_accessor :user_agent
|
20
|
+
|
21
|
+
# Set the logger.
|
22
|
+
attr_accessor :logger
|
23
|
+
|
24
|
+
# Set the JSON parser.
|
25
|
+
attr_accessor :json_parser
|
26
|
+
|
27
|
+
# Set the XML parser.
|
28
|
+
attr_accessor :xml_parser
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@timeout = 10
|
32
|
+
@redirect_limit = 5
|
33
|
+
@follow_redirect = true
|
34
|
+
@user_agent = "Aitch/#{Aitch::VERSION} (http://rubygems.org/gems/aitch)"
|
35
|
+
@default_headers = {}
|
36
|
+
@json_parser = JSON
|
37
|
+
@xml_parser = XMLParser
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.configure(&block)
|
42
|
+
yield configuration
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.configuration
|
46
|
+
@configuration ||= Configuration.new
|
47
|
+
end
|
48
|
+
end
|
data/lib/aitch/errors.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Aitch
|
2
|
+
InvalidURIError = Class.new(StandardError)
|
3
|
+
InvalidHTTPMethodError = Class.new(StandardError)
|
4
|
+
RequestTimeoutError = Class.new(StandardError)
|
5
|
+
TooManyRedirectsError = Class.new(StandardError)
|
6
|
+
|
7
|
+
ResponseError = Class.new(StandardError)
|
8
|
+
BadRequestError = Class.new(ResponseError)
|
9
|
+
UnauthorizedError = Class.new(ResponseError)
|
10
|
+
PaymentRequiredError = Class.new(ResponseError)
|
11
|
+
ForbiddenError = Class.new(ResponseError)
|
12
|
+
NotFoundError = Class.new(ResponseError)
|
13
|
+
MethodNotAllowedError = Class.new(ResponseError)
|
14
|
+
NotAcceptableError = Class.new(ResponseError)
|
15
|
+
ProxyAuthenticationRequiredError = Class.new(ResponseError)
|
16
|
+
RequestTimeOutError = Class.new(ResponseError)
|
17
|
+
ConflictError = Class.new(ResponseError)
|
18
|
+
GoneError = Class.new(ResponseError)
|
19
|
+
LengthRequiredError = Class.new(ResponseError)
|
20
|
+
PreconditionFailedError = Class.new(ResponseError)
|
21
|
+
RequestEntityTooLargeError = Class.new(ResponseError)
|
22
|
+
RequestURITooLongError = Class.new(ResponseError)
|
23
|
+
UnsupportedMediaTypeError = Class.new(ResponseError)
|
24
|
+
RequestedRangeNotSatisfiableError = Class.new(ResponseError)
|
25
|
+
ExpectationFailedError = Class.new(ResponseError)
|
26
|
+
UnprocessableEntityError = Class.new(ResponseError)
|
27
|
+
LockedError = Class.new(ResponseError)
|
28
|
+
FailedDependencyError = Class.new(ResponseError)
|
29
|
+
UpgradeRequiredError = Class.new(ResponseError)
|
30
|
+
PreconditionRequiredError = Class.new(ResponseError)
|
31
|
+
TooManyRequestsError = Class.new(ResponseError)
|
32
|
+
RequestHeaderFieldsTooLargeError = Class.new(ResponseError)
|
33
|
+
InternalServerErrorError = Class.new(ResponseError)
|
34
|
+
NotImplementedError = Class.new(ResponseError)
|
35
|
+
BadGatewayError = Class.new(ResponseError)
|
36
|
+
ServiceUnavailableError = Class.new(ResponseError)
|
37
|
+
GatewayTimeOutError = Class.new(ResponseError)
|
38
|
+
VersionNotSupportedError = Class.new(ResponseError)
|
39
|
+
InsufficientStorageError = Class.new(ResponseError)
|
40
|
+
NetworkAuthenticationRequiredError = Class.new(ResponseError)
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Aitch
|
2
|
+
class Redirect
|
3
|
+
def initialize
|
4
|
+
@tries = 1
|
5
|
+
@max_tries = Aitch.configuration.redirect_limit
|
6
|
+
end
|
7
|
+
|
8
|
+
def followed!
|
9
|
+
@tries += 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def follow?(response)
|
13
|
+
enabled? && response.redirect? && @tries < @max_tries
|
14
|
+
end
|
15
|
+
|
16
|
+
def enabled?
|
17
|
+
Aitch.configuration.follow_redirect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Aitch
|
2
|
+
class Request
|
3
|
+
def initialize(request_method, url, args = {}, headers = {}, options = {})
|
4
|
+
@request_method = request_method
|
5
|
+
@url = url
|
6
|
+
@args = args
|
7
|
+
@headers = headers
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform
|
12
|
+
response = Response.new(client.request(request))
|
13
|
+
redirect = Redirect.new
|
14
|
+
|
15
|
+
while redirect.follow?(response)
|
16
|
+
redirect.followed!
|
17
|
+
response = Aitch.get(response.location)
|
18
|
+
end
|
19
|
+
|
20
|
+
raise TooManyRedirectsError if redirect.enabled? && response.redirect?
|
21
|
+
|
22
|
+
response
|
23
|
+
rescue timeout_exception
|
24
|
+
raise RequestTimeoutError
|
25
|
+
end
|
26
|
+
|
27
|
+
def request
|
28
|
+
@request ||= http_method_class.new(File.join("/", uri.path)).tap do |request|
|
29
|
+
set_body(request)
|
30
|
+
set_user_agent(request)
|
31
|
+
set_gzip(request)
|
32
|
+
set_headers(request)
|
33
|
+
set_credentials(request)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def client
|
38
|
+
@client ||= Net::HTTP.new(uri.host, uri.port).tap do |client|
|
39
|
+
set_https(client)
|
40
|
+
set_timeout(client)
|
41
|
+
set_logger(client)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def uri
|
46
|
+
@uri ||= URI.parse(@url)
|
47
|
+
rescue URI::InvalidURIError
|
48
|
+
raise InvalidURIError
|
49
|
+
end
|
50
|
+
|
51
|
+
def http_method_class
|
52
|
+
Net::HTTP.const_get(@request_method.to_s.capitalize)
|
53
|
+
rescue NameError
|
54
|
+
raise InvalidHTTPMethodError, "unexpected HTTP verb: #{@request_method.inspect}"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def set_body(request)
|
59
|
+
if @args.respond_to?(:to_h)
|
60
|
+
request.form_data = @args.to_h
|
61
|
+
elsif @args.kind_of?(Hash)
|
62
|
+
request.form_data = @args
|
63
|
+
else
|
64
|
+
request.body = @args.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_headers(request)
|
69
|
+
all_headers = Aitch.configuration.default_headers.merge(@headers)
|
70
|
+
all_headers.each do |name, value|
|
71
|
+
request[name.to_s] = value.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_credentials(request)
|
76
|
+
request.basic_auth(@options[:user], @options[:password]) if @options[:user]
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_https(client)
|
80
|
+
client.use_ssl = uri.scheme == "https"
|
81
|
+
client.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_timeout(client)
|
85
|
+
client.read_timeout = Aitch.configuration.timeout
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_logger(client)
|
89
|
+
logger = Aitch.configuration.logger
|
90
|
+
client.set_debug_output(logger) if logger
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_user_agent(request)
|
94
|
+
request["User-Agent"] = Aitch.configuration.user_agent
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_gzip(request)
|
98
|
+
request["Accept-Encoding"] = "gzip,deflate"
|
99
|
+
end
|
100
|
+
|
101
|
+
def timeout_exception
|
102
|
+
defined?(Net::ReadTimeout) ? Net::ReadTimeout : Timeout::Error
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Aitch
|
2
|
+
class Response
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@http_response, :content_type
|
6
|
+
|
7
|
+
def initialize(http_response)
|
8
|
+
@http_response = http_response
|
9
|
+
end
|
10
|
+
|
11
|
+
def code
|
12
|
+
@http_response.code.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def body
|
16
|
+
@body ||= Body.new(@http_response).to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def success?
|
20
|
+
code >= 200 && code <= 399
|
21
|
+
end
|
22
|
+
|
23
|
+
def redirect?
|
24
|
+
code >= 300 && code <= 399
|
25
|
+
end
|
26
|
+
|
27
|
+
def error?
|
28
|
+
code >= 400 && code <= 599
|
29
|
+
end
|
30
|
+
|
31
|
+
def error
|
32
|
+
error? && ERRORS.fetch(code)
|
33
|
+
end
|
34
|
+
|
35
|
+
def json?
|
36
|
+
content_type =~ /json/
|
37
|
+
end
|
38
|
+
|
39
|
+
def xml?
|
40
|
+
content_type =~ /xml/
|
41
|
+
end
|
42
|
+
|
43
|
+
def html?
|
44
|
+
content_type =~ /html/
|
45
|
+
end
|
46
|
+
|
47
|
+
def data
|
48
|
+
if json?
|
49
|
+
Aitch.configuration.json_parser.load(body)
|
50
|
+
elsif xml?
|
51
|
+
Aitch.configuration.xml_parser.load(body)
|
52
|
+
else
|
53
|
+
body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def headers
|
58
|
+
@headers ||= {}.tap do |headers|
|
59
|
+
@http_response.each_header do |name, value|
|
60
|
+
headers[name.gsub(/^x-/, "")] = value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(name, *args, &block)
|
66
|
+
return headers[name.to_s] if headers.key?(name.to_s)
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond_to_missing?(name, include_private = false)
|
71
|
+
headers.key?(name.to_s)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aitch
|
2
|
+
class Response
|
3
|
+
class Body
|
4
|
+
def initialize(response)
|
5
|
+
@response = response
|
6
|
+
@body = response.body
|
7
|
+
@encoding = @response["content-encoding"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def gzip?
|
11
|
+
@encoding == "gzip"
|
12
|
+
end
|
13
|
+
|
14
|
+
def deflate?
|
15
|
+
@encoding == "deflate"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
if gzip?
|
20
|
+
Zlib::GzipReader.new(StringIO.new(@body)).read
|
21
|
+
elsif deflate?
|
22
|
+
Zlib::Inflate.inflate(@body)
|
23
|
+
else
|
24
|
+
@body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Aitch
|
2
|
+
class Response
|
3
|
+
ERRORS = {
|
4
|
+
400 => BadRequestError,
|
5
|
+
401 => UnauthorizedError,
|
6
|
+
402 => PaymentRequiredError,
|
7
|
+
403 => ForbiddenError,
|
8
|
+
404 => NotFoundError,
|
9
|
+
405 => MethodNotAllowedError,
|
10
|
+
406 => NotAcceptableError,
|
11
|
+
407 => ProxyAuthenticationRequiredError,
|
12
|
+
408 => RequestTimeOutError,
|
13
|
+
409 => ConflictError,
|
14
|
+
410 => GoneError,
|
15
|
+
411 => LengthRequiredError,
|
16
|
+
412 => PreconditionFailedError,
|
17
|
+
413 => RequestEntityTooLargeError,
|
18
|
+
414 => RequestURITooLongError,
|
19
|
+
415 => UnsupportedMediaTypeError,
|
20
|
+
416 => RequestedRangeNotSatisfiableError,
|
21
|
+
417 => ExpectationFailedError,
|
22
|
+
422 => UnprocessableEntityError,
|
23
|
+
423 => LockedError,
|
24
|
+
424 => FailedDependencyError,
|
25
|
+
426 => UpgradeRequiredError,
|
26
|
+
428 => PreconditionRequiredError,
|
27
|
+
429 => TooManyRequestsError,
|
28
|
+
431 => RequestHeaderFieldsTooLargeError,
|
29
|
+
500 => InternalServerErrorError,
|
30
|
+
501 => NotImplementedError,
|
31
|
+
502 => BadGatewayError,
|
32
|
+
503 => ServiceUnavailableError,
|
33
|
+
504 => GatewayTimeOutError,
|
34
|
+
505 => VersionNotSupportedError,
|
35
|
+
507 => InsufficientStorageError,
|
36
|
+
511 => NetworkAuthenticationRequiredError
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Aitch do
|
4
|
+
context "request methods" do
|
5
|
+
it { should respond_to(:get) }
|
6
|
+
it { should respond_to(:get!) }
|
7
|
+
it { should respond_to(:post) }
|
8
|
+
it { should respond_to(:post!) }
|
9
|
+
it { should respond_to(:put) }
|
10
|
+
it { should respond_to(:put!) }
|
11
|
+
it { should respond_to(:patch) }
|
12
|
+
it { should respond_to(:patch!) }
|
13
|
+
it { should respond_to(:delete) }
|
14
|
+
it { should respond_to(:delete!) }
|
15
|
+
it { should respond_to(:head) }
|
16
|
+
it { should respond_to(:head!) }
|
17
|
+
it { should respond_to(:options) }
|
18
|
+
it { should respond_to(:options!) }
|
19
|
+
it { should respond_to(:trace) }
|
20
|
+
it { should respond_to(:trace!) }
|
21
|
+
it { should respond_to(:execute) }
|
22
|
+
it { should respond_to(:execute!) }
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#execute" do
|
26
|
+
let(:request) { mock.as_null_object }
|
27
|
+
|
28
|
+
it "delegates to Request" do
|
29
|
+
Aitch::Request
|
30
|
+
.should_receive(:new)
|
31
|
+
.with("METHOD", "URL", "ARGS", "HEADERS", "OPTIONS")
|
32
|
+
.and_return(request)
|
33
|
+
|
34
|
+
Aitch.execute("METHOD", "URL", "ARGS", "HEADERS", "OPTIONS")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "performs request" do
|
38
|
+
Aitch::Request.stub new: request
|
39
|
+
request.should_receive(:perform)
|
40
|
+
|
41
|
+
Aitch.execute("METHOD", "URL")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#execute!" do
|
46
|
+
it "returns response when successful" do
|
47
|
+
response = stub(error?: false)
|
48
|
+
Aitch::Request.any_instance.stub perform: response
|
49
|
+
|
50
|
+
expect(Aitch.execute!("METHOD", "URL")).to eql(response)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "raises when has errors" do
|
54
|
+
response = stub(error?: true, error: "ERROR")
|
55
|
+
Aitch::Request.any_instance.stub perform: response
|
56
|
+
|
57
|
+
expect {
|
58
|
+
Aitch.execute!("METHOD", "URL")
|
59
|
+
}.to raise_error("ERROR")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Aitch::Configuration do
|
4
|
+
it "sets default timeout" do
|
5
|
+
expect(Aitch::Configuration.new.timeout).to eql(10)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "sets default user agent" do
|
9
|
+
user_agent = "Aitch/#{Aitch::VERSION} (http://rubygems.org/gems/aitch)"
|
10
|
+
expect(Aitch::Configuration.new.user_agent).to eql(user_agent)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "sets default maximum redirections" do
|
14
|
+
expect(Aitch::Configuration.new.redirect_limit).to eql(5)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets default headers" do
|
18
|
+
expect(Aitch::Configuration.new.default_headers).to eql({})
|
19
|
+
end
|
20
|
+
|
21
|
+
it "sets default XML parser" do
|
22
|
+
expect(Aitch::Configuration.new.xml_parser).to eql(Aitch::XMLParser)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "configures aitch" do
|
26
|
+
Aitch.configure do |config|
|
27
|
+
config.timeout = 15
|
28
|
+
end
|
29
|
+
|
30
|
+
expect(Aitch.configuration.timeout).to eql(15)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Aitch::Request do
|
4
|
+
it "raises with invalid uri" do
|
5
|
+
expect {
|
6
|
+
Aitch::Request.new("post", "\\").uri
|
7
|
+
}.to raise_error(Aitch::InvalidURIError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "raises on timeout", ruby: 2.0 do
|
11
|
+
request = Aitch::Request.new("post", "http://example.org")
|
12
|
+
request.stub_chain(:client, :request).and_raise(Net::ReadTimeout)
|
13
|
+
|
14
|
+
expect {
|
15
|
+
request.perform
|
16
|
+
}.to raise_error(Aitch::RequestTimeoutError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "raises on timeout", ruby: 1.9 do
|
20
|
+
request = Aitch::Request.new("post", "http://example.org")
|
21
|
+
request.stub_chain(:client, :request).and_raise(Timeout::Error)
|
22
|
+
|
23
|
+
expect {
|
24
|
+
request.perform
|
25
|
+
}.to raise_error(Aitch::RequestTimeoutError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "sets user agent" do
|
29
|
+
request = Aitch::Request.new("post", "http://example.org/some/path").request
|
30
|
+
expect(request["User-Agent"]).to eql(Aitch.configuration.user_agent)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "requests gzip encoding" do
|
34
|
+
request = Aitch::Request.new("get", "http://example.org").request
|
35
|
+
expect(request["Accept-Encoding"]).to eql("gzip,deflate")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "sets path" do
|
39
|
+
request = Aitch::Request.new("post", "http://example.org/some/path").request
|
40
|
+
expect(request.path).to eql("/some/path")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sets request body from hash" do
|
44
|
+
request = Aitch::Request.new("post", "http://example.org/", {a: 1}).request
|
45
|
+
expect(request.body).to eql("a=1")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "sets request body from string" do
|
49
|
+
request = Aitch::Request.new("post", "http://example.org/", "some body").request
|
50
|
+
expect(request.body).to eql("some body")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets request body from to_h protocol" do
|
54
|
+
data = stub(to_h: {a: 1})
|
55
|
+
request = Aitch::Request.new("post", "http://example.org/", data).request
|
56
|
+
expect(request.body).to eql("a=1")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "sets request body from to_s protocol" do
|
60
|
+
data = stub(to_s: "some body")
|
61
|
+
request = Aitch::Request.new("post", "http://example.org/", data).request
|
62
|
+
|
63
|
+
expect(request.body).to eql("some body")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "sets default headers" do
|
67
|
+
Aitch.configuration.default_headers = {"HEADER" => "VALUE"}
|
68
|
+
request = Aitch::Request.new("post", "http://example.org/").request
|
69
|
+
expect(request["HEADER"]).to eql("VALUE")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "sets custom headers" do
|
73
|
+
request = Aitch::Request.new("post", "http://example.org/", {}, {"HEADER" => "VALUE"}).request
|
74
|
+
expect(request["HEADER"]).to eql("VALUE")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "sets basic auth credentials" do
|
78
|
+
request = Aitch::Request.new("post", "http://example.org/", {}, {}, {user: "USER", password: "PASS"}).request
|
79
|
+
credentials = Base64.decode64(request["Authorization"].gsub(/Basic /, ""))
|
80
|
+
|
81
|
+
expect(credentials).to eql("USER:PASS")
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#client" do
|
85
|
+
context "https" do
|
86
|
+
subject(:client) {
|
87
|
+
Aitch::Request.new("get", "https://example.org").client
|
88
|
+
}
|
89
|
+
|
90
|
+
it "sets https" do
|
91
|
+
expect(client.use_ssl?).to be_true
|
92
|
+
end
|
93
|
+
|
94
|
+
it "sets verification mode" do
|
95
|
+
expect(client.verify_mode).to eql(OpenSSL::SSL::VERIFY_PEER)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "sets timeout" do
|
99
|
+
Aitch.configuration.timeout = 20
|
100
|
+
expect(client.read_timeout).to eql(20)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "Request class" do
|
106
|
+
it "raises with invalid method" do
|
107
|
+
expect {
|
108
|
+
Aitch::Request.new("invalid", "URL").request
|
109
|
+
}.to raise_error(Aitch::InvalidHTTPMethodError, %[unexpected HTTP verb: "invalid"])
|
110
|
+
end
|
111
|
+
|
112
|
+
%w[
|
113
|
+
get
|
114
|
+
post
|
115
|
+
put
|
116
|
+
patch
|
117
|
+
delete
|
118
|
+
head
|
119
|
+
options
|
120
|
+
trace
|
121
|
+
].each do |method|
|
122
|
+
it "instantiates #{method.upcase} method" do
|
123
|
+
request = Aitch::Request.new(method, "URL").request
|
124
|
+
expect(request.class.name).to eql("Net::HTTP::#{method.capitalize}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "follow redirection" do
|
130
|
+
before { Aitch.configuration.follow_redirect = true }
|
131
|
+
|
132
|
+
it "follows redirect" do
|
133
|
+
Aitch.configuration.redirect_limit = 5
|
134
|
+
|
135
|
+
FakeWeb.register_uri(:get, "http://example.org/", location: "http://example.com/", status: 301)
|
136
|
+
FakeWeb.register_uri(:get, "http://example.com/", body: "Hello")
|
137
|
+
|
138
|
+
response = Aitch.get("http://example.org")
|
139
|
+
|
140
|
+
expect(response).not_to be_redirect
|
141
|
+
expect(response.body).to eql("Hello")
|
142
|
+
end
|
143
|
+
|
144
|
+
it "raises when doing too many redirects" do
|
145
|
+
Aitch.configuration.redirect_limit = 1
|
146
|
+
|
147
|
+
FakeWeb.register_uri(:get, "http://example.org/", location: "http://example.com/", status: 301)
|
148
|
+
FakeWeb.register_uri(:get, "http://example.com/", location: "https://example.com/", status: 301)
|
149
|
+
|
150
|
+
expect {
|
151
|
+
Aitch.get("http://example.org/")
|
152
|
+
}.to raise_error(Aitch::TooManyRedirectsError)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Aitch::Response do
|
4
|
+
it "has body" do
|
5
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "Hello")
|
6
|
+
response = Aitch.get("http://example.org/")
|
7
|
+
expect(response.body).to eql("Hello")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "parses gzip response" do
|
11
|
+
stdio = StringIO.new
|
12
|
+
gzipped = Zlib::GzipWriter.new(stdio)
|
13
|
+
gzipped.write("Hello")
|
14
|
+
gzipped.finish
|
15
|
+
|
16
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: stdio.string, content_encoding: "gzip")
|
17
|
+
response = Aitch.get("http://example.org/")
|
18
|
+
|
19
|
+
expect(response.body).to eql("Hello")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "deflates response" do
|
23
|
+
stdio = StringIO.new
|
24
|
+
deflated = Zlib::Deflate.deflate("Hello")
|
25
|
+
|
26
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: deflated, content_encoding: "deflate")
|
27
|
+
response = Aitch.get("http://example.org/")
|
28
|
+
|
29
|
+
expect(response.body).to eql("Hello")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns status code" do
|
33
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "")
|
34
|
+
response = Aitch.get("http://example.org/")
|
35
|
+
expect(response.code).to eql(200)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns content type" do
|
39
|
+
FakeWeb.register_uri(:get, "http://example.org/", content_type: "text/html")
|
40
|
+
response = Aitch.get("http://example.org/")
|
41
|
+
expect(response.content_type).to eql("text/html")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "detects as successful response" do
|
45
|
+
FakeWeb.register_uri(:get, "http://example.org/", content_type: "text/html")
|
46
|
+
response = Aitch.get("http://example.org/")
|
47
|
+
expect(response).to be_success
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns headers" do
|
51
|
+
FakeWeb.register_uri(:get, "http://example.org/", content_type: "text/html")
|
52
|
+
headers = Aitch.get("http://example.org/").headers
|
53
|
+
|
54
|
+
expect(headers).to be_a(Hash)
|
55
|
+
expect(headers["content-type"]).to eql("text/html")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "normalizes custom headers" do
|
59
|
+
FakeWeb.register_uri(:get, "http://example.org/", "X-Runtime" => "0.003")
|
60
|
+
headers = Aitch.get("http://example.org/").headers
|
61
|
+
|
62
|
+
expect(headers["runtime"]).to eql("0.003")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "maps missing methods to headers" do
|
66
|
+
FakeWeb.register_uri(:get, "http://example.org/", "X-Runtime" => "0.003")
|
67
|
+
response = Aitch.get("http://example.org/")
|
68
|
+
|
69
|
+
expect(response.runtime).to eql("0.003")
|
70
|
+
expect(response).to respond_to(:runtime)
|
71
|
+
end
|
72
|
+
|
73
|
+
context "status 3xx" do
|
74
|
+
before { Aitch.configuration.follow_redirect = false }
|
75
|
+
|
76
|
+
it "has body" do
|
77
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "Hello", status: 301)
|
78
|
+
response = Aitch.get("http://example.org/")
|
79
|
+
expect(response.body).to eql("Hello")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "detects as successful response" do
|
83
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 301)
|
84
|
+
response = Aitch.get("http://example.org/")
|
85
|
+
expect(response).to be_success
|
86
|
+
end
|
87
|
+
|
88
|
+
it "detects as redirect" do
|
89
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 301)
|
90
|
+
response = Aitch.get("http://example.org/")
|
91
|
+
expect(response).to be_redirect
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns location" do
|
95
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 301, location: "https://example.com/")
|
96
|
+
response = Aitch.get("http://example.org/")
|
97
|
+
expect(response.location).to eql("https://example.com/")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "status 4xx" do
|
102
|
+
it "detects as error" do
|
103
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 404)
|
104
|
+
response = Aitch.get("http://example.org/")
|
105
|
+
|
106
|
+
expect(response).to be_error
|
107
|
+
end
|
108
|
+
|
109
|
+
it "sets error" do
|
110
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 404)
|
111
|
+
response = Aitch.get("http://example.org/")
|
112
|
+
|
113
|
+
expect(response.error).to eql(Aitch::NotFoundError)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "status 5xx" do
|
118
|
+
it "detects as error" do
|
119
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 500)
|
120
|
+
response = Aitch.get("http://example.org/")
|
121
|
+
|
122
|
+
expect(response).to be_error
|
123
|
+
end
|
124
|
+
|
125
|
+
it "sets error" do
|
126
|
+
FakeWeb.register_uri(:get, "http://example.org/", status: 500)
|
127
|
+
response = Aitch.get("http://example.org/")
|
128
|
+
|
129
|
+
expect(response.error).to eql(Aitch::InternalServerErrorError)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "JSON" do
|
134
|
+
it "detects as json" do
|
135
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "[]", content_type: "application/json")
|
136
|
+
response = Aitch.get("http://example.org/")
|
137
|
+
|
138
|
+
expect(response).to be_json
|
139
|
+
end
|
140
|
+
|
141
|
+
it "returns data" do
|
142
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "[1,2,3]", content_type: "application/json")
|
143
|
+
response = Aitch.get("http://example.org/")
|
144
|
+
|
145
|
+
expect(response.data).to eql([1,2,3])
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "HTML" do
|
150
|
+
it "detects as html" do
|
151
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "", content_type: "text/html")
|
152
|
+
response = Aitch.get("http://example.org/")
|
153
|
+
|
154
|
+
expect(response).to be_html
|
155
|
+
end
|
156
|
+
|
157
|
+
it "returns data" do
|
158
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "Hello", content_type: "text/html")
|
159
|
+
response = Aitch.get("http://example.org/")
|
160
|
+
|
161
|
+
expect(response.data).to eql("Hello")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "XML" do
|
166
|
+
it "detects as xml" do
|
167
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "[]", content_type: "application/xml")
|
168
|
+
response = Aitch.get("http://example.org/")
|
169
|
+
|
170
|
+
expect(response).to be_xml
|
171
|
+
end
|
172
|
+
|
173
|
+
it "returns data" do
|
174
|
+
FakeWeb.register_uri(:get, "http://example.org/", body: "<foo/>", content_type: "application/xml")
|
175
|
+
response = Aitch.get("http://example.org/")
|
176
|
+
|
177
|
+
expect(response.data).to be_a(Nokogiri::XML::Document)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require
|
3
|
+
|
4
|
+
require "aitch"
|
5
|
+
require "base64"
|
6
|
+
require "test_notifier/runner/rspec"
|
7
|
+
require "fakeweb"
|
8
|
+
require "nokogiri"
|
9
|
+
|
10
|
+
FakeWeb.allow_net_connect = false
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.filter_run_excluding :ruby => -> version {
|
14
|
+
!(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
|
15
|
+
}
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aitch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nando Vieira
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: pry-meta
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: test_notifier
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: fakeweb
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: nokogiri
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: A simple HTTP client
|
127
|
+
email:
|
128
|
+
- fnando.vieira@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .rspec
|
135
|
+
- .travis.yml
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- aitch.gemspec
|
141
|
+
- lib/aitch.rb
|
142
|
+
- lib/aitch/configuration.rb
|
143
|
+
- lib/aitch/errors.rb
|
144
|
+
- lib/aitch/redirect.rb
|
145
|
+
- lib/aitch/request.rb
|
146
|
+
- lib/aitch/response.rb
|
147
|
+
- lib/aitch/response/body.rb
|
148
|
+
- lib/aitch/response/errors.rb
|
149
|
+
- lib/aitch/version.rb
|
150
|
+
- lib/aitch/xml_parser.rb
|
151
|
+
- spec/aitch/aitch_spec.rb
|
152
|
+
- spec/aitch/configuration_spec.rb
|
153
|
+
- spec/aitch/request_spec.rb
|
154
|
+
- spec/aitch/response_spec.rb
|
155
|
+
- spec/aitch/xml_parser_spec.rb
|
156
|
+
- spec/spec_helper.rb
|
157
|
+
homepage: http://rubygems.org/gems/aitch
|
158
|
+
licenses:
|
159
|
+
- MIT
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ! '>='
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
+
none: false
|
172
|
+
requirements:
|
173
|
+
- - ! '>='
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
requirements: []
|
177
|
+
rubyforge_project:
|
178
|
+
rubygems_version: 1.8.23
|
179
|
+
signing_key:
|
180
|
+
specification_version: 3
|
181
|
+
summary: A simple HTTP client
|
182
|
+
test_files:
|
183
|
+
- spec/aitch/aitch_spec.rb
|
184
|
+
- spec/aitch/configuration_spec.rb
|
185
|
+
- spec/aitch/request_spec.rb
|
186
|
+
- spec/aitch/response_spec.rb
|
187
|
+
- spec/aitch/xml_parser_spec.rb
|
188
|
+
- spec/spec_helper.rb
|