chimera_http_client 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rubocop.yml +95 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.markdown +180 -0
- data/Rakefile +22 -0
- data/TODO.markdown +56 -0
- data/chimera_http_client.gemspec +29 -0
- data/lib/chimera_http_client.rb +8 -0
- data/lib/chimera_http_client/connection.rb +99 -0
- data/lib/chimera_http_client/error.rb +76 -0
- data/lib/chimera_http_client/request.rb +71 -0
- data/lib/chimera_http_client/response.rb +22 -0
- data/lib/chimera_http_client/version.rb +3 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cd7ec7cbd4783fd8bd3c23a03ac5608c1357dfc7e300dc97a8ae721855b520f9
|
4
|
+
data.tar.gz: 28503993a05c765deb35bab4f263d47fb73bbc05ada7c3425a4b88b8531237d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f444bb5f204a7212648d22392a3881085269e8e355ad18bf165eb61ea3e468fd6aba06c6debdd418b8d6eba0c6a80f9ce21ef2f2c3656ee080972731ba37cdbf
|
7
|
+
data.tar.gz: 13e1706d0f6d0b3a1cce9087e35c675d78053d53f2c8c5c0f113669dcaa10d1bf0badda6437c9eb45e8a08b8c00ac5a45411886cb2144bb942efa1403151f5e6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
AllCops:
|
2
|
+
DisplayCopNames: true
|
3
|
+
TargetRubyVersion: 2.5
|
4
|
+
Exclude:
|
5
|
+
- vendor/**/*
|
6
|
+
- config.ru
|
7
|
+
|
8
|
+
# Cop supports --auto-correct.
|
9
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
|
10
|
+
# SupportedStyles: special_inside_parentheses, consistent, align_braces
|
11
|
+
Layout/IndentHash:
|
12
|
+
EnforcedStyle: consistent
|
13
|
+
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Max: 35
|
16
|
+
|
17
|
+
Metrics/BlockLength:
|
18
|
+
Exclude:
|
19
|
+
- spec/**/*
|
20
|
+
|
21
|
+
# Configuration parameters: CountComments.
|
22
|
+
Metrics/ClassLength:
|
23
|
+
Max: 150
|
24
|
+
|
25
|
+
Metrics/CyclomaticComplexity:
|
26
|
+
Max: 15
|
27
|
+
|
28
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
|
29
|
+
# URISchemes: http, https
|
30
|
+
Metrics/LineLength:
|
31
|
+
Max: 125
|
32
|
+
|
33
|
+
# Offense count: 5
|
34
|
+
# Configuration parameters: CountComments.
|
35
|
+
Metrics/MethodLength:
|
36
|
+
Max: 32
|
37
|
+
|
38
|
+
Metrics/ParameterLists:
|
39
|
+
Max: 7
|
40
|
+
|
41
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
42
|
+
# SupportedStyles: braces, no_braces, context_dependent
|
43
|
+
Style/BracesAroundHashParameters:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Style/CommentedKeyword:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Style/Documentation:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Style/DoubleNegation:
|
53
|
+
Enabled: true
|
54
|
+
|
55
|
+
Style/FrozenStringLiteralComment:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Style/ModuleFunction:
|
59
|
+
Enabled: false
|
60
|
+
|
61
|
+
Style/StringLiterals:
|
62
|
+
Description: 'Checks if uses of quotes match the configured preference.'
|
63
|
+
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
|
64
|
+
Enabled: true
|
65
|
+
EnforcedStyle: double_quotes
|
66
|
+
SupportedStyles:
|
67
|
+
- single_quotes
|
68
|
+
- double_quotes
|
69
|
+
|
70
|
+
# Cop supports --auto-correct.
|
71
|
+
Style/NumericLiterals:
|
72
|
+
MinDigits: 15
|
73
|
+
|
74
|
+
# Cop supports --auto-correct.
|
75
|
+
# Configuration parameters: AllowAsExpressionSeparator.
|
76
|
+
Style/Semicolon:
|
77
|
+
AllowAsExpressionSeparator: true
|
78
|
+
|
79
|
+
# Cop supports --auto-correct.
|
80
|
+
# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
|
81
|
+
# SupportedStyles: comma, consistent_comma, no_comma
|
82
|
+
Style/TrailingCommaInArrayLiteral:
|
83
|
+
EnforcedStyleForMultiline: comma
|
84
|
+
|
85
|
+
Style/TrailingCommaInHashLiteral:
|
86
|
+
EnforcedStyleForMultiline: comma
|
87
|
+
|
88
|
+
# Cop supports --auto-correct.
|
89
|
+
# Configuration parameters: SupportedStyles, MinSize, WordRegex.
|
90
|
+
# SupportedStyles: percent, brackets
|
91
|
+
Style/WordArray:
|
92
|
+
EnforcedStyle: percent
|
93
|
+
|
94
|
+
Style/SignalException:
|
95
|
+
EnforcedStyle: semantic
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.2
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017-2020 Andreas Finger
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# ChimeraHttpClient
|
2
|
+
|
3
|
+
When starting to split monolithic apps into smaller services, you need an easy way to access the remote data from the other apps. This chimera_http_client gem should serve as a unified way to access endpoints from other apps. And what works for the internal communication between your own apps, will also work to work with external APIs that do not offer a client for simplified access.
|
4
|
+
|
5
|
+
## Dependencies
|
6
|
+
|
7
|
+
The `chimera_http_client` gem is using the _libcurl_ wrapper [**Typhoeus**](https://typhoeus.github.io/). This allows for fast requests, for caching, and for queueing requests to run them in parallel (queueing and parallel requests are not implemented in the gem yet).
|
8
|
+
|
9
|
+
It does not have any other runtime dependencies.
|
10
|
+
|
11
|
+
### optional
|
12
|
+
|
13
|
+
Setting the environment variable `ENV['CHIMERA_HTTP_CLIENT_LOG_REQUESTS']` to `true` (or `'true'`) will provide more detailed error messages for logging and also add additional information to the Error JSON. It is recommended to use this only in development environments.
|
14
|
+
|
15
|
+
## The Connection class
|
16
|
+
|
17
|
+
The basic usage looks like this:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
connection = ChimeraHttpClient::Connection.new(base_url: 'http://localhost/namespace')
|
21
|
+
response = connection.get!(endpoint, params: params)
|
22
|
+
```
|
23
|
+
|
24
|
+
`ChimeraHttpClient::Connection.new` expects an options hash as parameter. The only required option is **base_url** which should include the host, port and base path to the API endpoints you want to call, e.g.
|
25
|
+
`base_url: 'http://localhost:3000/v1'`.
|
26
|
+
|
27
|
+
On this connection object, you can call methods like `#get!` or `#post!` with an endpoint and an options hash as parameters, e.g.
|
28
|
+
`connection.get!("users/#{id}")` or
|
29
|
+
`connection.get(['users', id], options: { headers: { ' Accept-Charset' => 'utf-8' })`
|
30
|
+
|
31
|
+
Please take note that _the endpoint can be given as a String, a Symbol, or as an Array._
|
32
|
+
While they do no harm, there is _no need to pass leading or trailing `/` in endpoints._
|
33
|
+
When passing the endpoint as an Array, _it's elements are converted to Strings and concatenated with `/`._
|
34
|
+
On each request _the http-headers can be amended or overwritten_ completely or partially.
|
35
|
+
|
36
|
+
In case you need to use an API that is protected by **basic_auth** just pass the credentials in the options hash:
|
37
|
+
`options: { username: 'admin', password: 'secret' }`
|
38
|
+
|
39
|
+
### Example usage
|
40
|
+
|
41
|
+
To use the gem, it is recommended to write wrapper classes for the endpoints used. While it would be possible to use the `get, get!, post, post!, put, put!, patch, patch!, delete, delete!` or also the bare `request.run` methods directly, wrapper classes will unify the usage pattern and be very convenient to use by veterans and newcomers to the team. A wrapper class could look like this:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
require 'chimera_http_client'
|
45
|
+
|
46
|
+
class Users
|
47
|
+
def initialize(base_url: 'http://localhost:3000/v1')
|
48
|
+
@base_url = base_url
|
49
|
+
end
|
50
|
+
|
51
|
+
def find(id:)
|
52
|
+
response = connection.get!(['users', id])
|
53
|
+
|
54
|
+
user = response.parsed_body
|
55
|
+
User.new(id: id, name: user['name'], email: user['email'])
|
56
|
+
|
57
|
+
rescue ChimeraHttpClient::Error => error
|
58
|
+
# handle / log / raise error
|
59
|
+
end
|
60
|
+
|
61
|
+
def all(filter: nil, page: nil)
|
62
|
+
params = {}
|
63
|
+
params[:filter] = filter
|
64
|
+
params[:page] = page
|
65
|
+
|
66
|
+
response = connection.get!('users', params: params)
|
67
|
+
|
68
|
+
all_users = response.parsed_body
|
69
|
+
all_users.map { |user| User.new(id: user['id'], name: user['name'], email: user['email']) }
|
70
|
+
|
71
|
+
rescue ChimeraHttpClient::Error => error
|
72
|
+
# handle / log / raise error
|
73
|
+
end
|
74
|
+
|
75
|
+
def create(body:)
|
76
|
+
response = connection.post!('users', body: body.to_json) # body.to_json
|
77
|
+
|
78
|
+
user = response.parsed_body
|
79
|
+
User.new(id: user['id'], name: user['name'], email: user['email'])
|
80
|
+
|
81
|
+
rescue ChimeraHttpClient::Error => error
|
82
|
+
# handle / log / raise error
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def connection
|
88
|
+
@connection ||= ChimeraHttpClient::Connection.new(base_url: @base_url)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
To create and fetch a user from a remote service with the `Users` wrapper listed above, calls could be made like this:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
users = Users.new
|
97
|
+
|
98
|
+
new_user = users.create(body: { name: "Andy", email: "andy@example.com" })
|
99
|
+
id = new_user.id
|
100
|
+
|
101
|
+
user = users.find(id: id)
|
102
|
+
user.name # == "Andy"
|
103
|
+
```
|
104
|
+
|
105
|
+
## The Request class
|
106
|
+
|
107
|
+
Usually it does not have to be used directly. It is the class that executes the `Typhoeus::Requests`, raises `Errors` on failing and returns `Response` objects on successful calls.
|
108
|
+
|
109
|
+
It will be expanded by a `.queue` method, that will queue (sic) calls and run them in parallel and not run every call directly, like the `.run` method does.
|
110
|
+
|
111
|
+
## The Response class
|
112
|
+
|
113
|
+
The `ChimeraHttpClient::Response` objects have the following interface:
|
114
|
+
|
115
|
+
* body (content the call returns)
|
116
|
+
* code (http code, should be 200 or 2xx)
|
117
|
+
* time (for monitoring)
|
118
|
+
* response (the full response object, including the request)
|
119
|
+
* error? (returns false)
|
120
|
+
* parsed_body (returns the result of JSON.parse(body))
|
121
|
+
|
122
|
+
If your API does not use JSON, but a different format e.g. XML, you can either monkey patch a `parsed_xml` method to the Response class, or let your wrapper handle the parsing of `body`.
|
123
|
+
|
124
|
+
## Error classes
|
125
|
+
|
126
|
+
All errors inherit from `ChimeraHttpClient::Error` and therefore offer the same attributes:
|
127
|
+
|
128
|
+
* code (http error code)
|
129
|
+
* body (alias => message)
|
130
|
+
* time (for monitoring)
|
131
|
+
* response (the full response object, including the request)
|
132
|
+
* error? (returns true)
|
133
|
+
* error_class (e.g. ChimeraHttpClient::NotFoundError)
|
134
|
+
* to_s (information for logging / respects ENV['CHIMERA_HTTP_CLIENT_LOG_REQUESTS'])
|
135
|
+
* to_json (information to return to the API consumer / respects ENV['CHIMERA_HTTP_CLIENT_LOG_REQUESTS'])
|
136
|
+
|
137
|
+
The error classes and their corresponding http error codes:
|
138
|
+
|
139
|
+
ConnectionError # 0
|
140
|
+
RedirectionError # 301, 302, 303, 307
|
141
|
+
BadRequestError # 400
|
142
|
+
UnauthorizedError # 401
|
143
|
+
ForbiddenError # 403
|
144
|
+
NotFoundError # 404
|
145
|
+
MethodNotAllowedError # 405
|
146
|
+
ResourceConflictError # 409
|
147
|
+
UnprocessableEntityError # 422
|
148
|
+
ClientError # 400..499
|
149
|
+
ServerError # 500..599
|
150
|
+
TimeoutError # timeout
|
151
|
+
|
152
|
+
## Installation
|
153
|
+
|
154
|
+
Add this line to your application's Gemfile:
|
155
|
+
|
156
|
+
gem 'chimera_http_client', '~> 0.2'
|
157
|
+
|
158
|
+
And then execute:
|
159
|
+
|
160
|
+
$ bundle
|
161
|
+
|
162
|
+
When updating the version, do not forget to run
|
163
|
+
|
164
|
+
$ bundle update chimera_http_client
|
165
|
+
|
166
|
+
## Maintainers and Contributors
|
167
|
+
|
168
|
+
After checking out the repo, run `rake` to run the **tests and rubocop**.
|
169
|
+
|
170
|
+
You can also run `rake console` to open an irb session with the `ChimeraHttpClient` pre-loaded that will allow you to experiment.
|
171
|
+
|
172
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
173
|
+
|
174
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/mediafinger/chimera_http_client>
|
175
|
+
|
176
|
+
## Chimera
|
177
|
+
|
178
|
+
Why this name? First of all, I needed a unique namespace. _HttpClient_ is already used too often. And as this gem is based on **Typhoeus** I picked the name of one of his (mythological) children.
|
179
|
+
|
180
|
+
<https://en.wikipedia.org/wiki/Chimera_(mythology)>
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:rspec)
|
5
|
+
|
6
|
+
desc "Open a console with the ChimeraHttpClient loaded"
|
7
|
+
task :console do
|
8
|
+
system "irb -r lib/chimera_http_client.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Run Rubocop"
|
12
|
+
task :rubocop do
|
13
|
+
system "bundle exec rubocop -c .rubocop.yml"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Run Rubocop and auto-correct issues"
|
17
|
+
task :rubocopa do
|
18
|
+
system "bundle exec rubocop -c .rubocop.yml -a"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Run all the tests"
|
22
|
+
task default: %i[rspec rubocop]
|
data/TODO.markdown
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# ChimeraHttpClient TODOs
|
2
|
+
|
3
|
+
## Bugs
|
4
|
+
|
5
|
+
* The connection_specs are testing for a Hash in the body instead for serialized JSON content
|
6
|
+
* The implementation seems to be buggy, not handling JSON as intended
|
7
|
+
|
8
|
+
## Features
|
9
|
+
|
10
|
+
### HTTP Headers
|
11
|
+
|
12
|
+
* make Connection#default_headers configurable
|
13
|
+
* allow to set default_headers via ENV vars
|
14
|
+
* give example how to use default_headers (e.g. request_id)
|
15
|
+
* explain in README how to benefit from this gem in a given setting
|
16
|
+
|
17
|
+
### Logger
|
18
|
+
|
19
|
+
* allow to pass a logger
|
20
|
+
* add logger.info when starting a http call
|
21
|
+
* add logger.info when finishing a successful http call
|
22
|
+
* add logger.warn / .error for error cases
|
23
|
+
* include the total_time of the requests in the log
|
24
|
+
* add example to README
|
25
|
+
|
26
|
+
### Custom Serializer
|
27
|
+
|
28
|
+
* allow to pass custom serializer
|
29
|
+
* use custom serializer instead of expecting a JSON (or other) body
|
30
|
+
* set header for the serializer
|
31
|
+
* add example to README
|
32
|
+
|
33
|
+
### Custom De-serializer
|
34
|
+
|
35
|
+
* allow to pass custom deserializer
|
36
|
+
* use custom deserializer in #parsed_body instead of default JSON parsing
|
37
|
+
* add example to README
|
38
|
+
|
39
|
+
### Queueing
|
40
|
+
|
41
|
+
* allow to queue multiple calls
|
42
|
+
* execute (up to 10) calls in parallel
|
43
|
+
* add example to README
|
44
|
+
|
45
|
+
### Caching
|
46
|
+
|
47
|
+
* optional per connection or call
|
48
|
+
* add example to README
|
49
|
+
|
50
|
+
### Release
|
51
|
+
|
52
|
+
* make repo public
|
53
|
+
* hook up Travis-CI
|
54
|
+
* ensure it runs with Ruby 2.5 and newer
|
55
|
+
* get feedback
|
56
|
+
* release to rubygems to add to the plethora of similar gems
|
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "chimera_http_client/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "chimera_http_client"
|
7
|
+
spec.version = ChimeraHttpClient::VERSION
|
8
|
+
spec.authors = ["Andreas Finger"]
|
9
|
+
spec.email = ["webmaster@mediafinger.com"]
|
10
|
+
|
11
|
+
spec.summary = "Http Client to connect to REST APIs"
|
12
|
+
spec.description = "General http client functionality to easily connect to JSON endpoints"
|
13
|
+
spec.homepage = "https://github.com/mediafinger/chimera_http_client"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "typhoeus", "~> 1.1"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
+
spec.add_development_dependency "irb", "~> 1.0"
|
25
|
+
spec.add_development_dependency "rake", ">= 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
spec.add_development_dependency "rubocop", "~> 0.50"
|
28
|
+
spec.add_development_dependency "rubocop-rspec", "~> 1.4"
|
29
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# The get, post, put, patch and delete methods return either a Response or a Error
|
2
|
+
# The bang methods get!, post!, put!, patch! and delete! raise a Error in case of failure
|
3
|
+
|
4
|
+
module ChimeraHttpClient
|
5
|
+
class Connection
|
6
|
+
USER_AGENT = "ChimeraHttpClient (by mediafinger)".freeze
|
7
|
+
|
8
|
+
def initialize(base_url:, user_agent: USER_AGENT, verbose: false)
|
9
|
+
fail(ChimeraHttpClient::ParameterMissingError, "base_url expected, but not given") if base_url.nil?
|
10
|
+
@base_url = base_url
|
11
|
+
|
12
|
+
define_bang_methods
|
13
|
+
|
14
|
+
Typhoeus::Config.user_agent = user_agent
|
15
|
+
Typhoeus::Config.verbose = verbose
|
16
|
+
Typhoeus::Config.memoize = false
|
17
|
+
# Typhoeus::Config.cache
|
18
|
+
end
|
19
|
+
|
20
|
+
def request
|
21
|
+
@request ||= Request.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(endpoint, options = {})
|
25
|
+
headers = extract_headers(options, default_headers)
|
26
|
+
|
27
|
+
request.run(url: url(endpoint), method: :get, options: options, headers: headers)
|
28
|
+
end
|
29
|
+
|
30
|
+
def post(endpoint, options = {})
|
31
|
+
run_with_body(:post, endpoint, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(endpoint, options = {})
|
35
|
+
run_with_body(:put, endpoint, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def patch(endpoint, options = {})
|
39
|
+
run_with_body(:patch, endpoint, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(endpoint, options = {})
|
43
|
+
run_with_body(:delete, endpoint, options.merge(body_optional: true))
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def run_with_body(method, endpoint, options = {})
|
49
|
+
body = extract_body(options)
|
50
|
+
headers = extract_headers(options, default_headers)
|
51
|
+
|
52
|
+
request.run(url: url(endpoint), method: method, body: body, options: options, headers: headers)
|
53
|
+
end
|
54
|
+
|
55
|
+
def url(endpoint)
|
56
|
+
trimmed_endpoint = Array(endpoint).map { |e| trim(e) }
|
57
|
+
[@base_url.chomp("/"), trimmed_endpoint].flatten.reject(&:empty?).join("/")
|
58
|
+
end
|
59
|
+
|
60
|
+
def trim(element)
|
61
|
+
element.to_s.sub(%r{^\/}, "").chomp("/")
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_body(options)
|
65
|
+
body = options.delete(:body)
|
66
|
+
body_optional = options.delete(:body_optional)
|
67
|
+
fail(ChimeraHttpClient::ParameterMissingError, "body expected, but not given") if body.nil? && !body_optional
|
68
|
+
body
|
69
|
+
end
|
70
|
+
|
71
|
+
def extract_headers(options, headers)
|
72
|
+
given_headers = options.delete(:headers) || {}
|
73
|
+
headers.merge(given_headers)
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_headers
|
77
|
+
{ "Content-Type" => "application/json" }
|
78
|
+
end
|
79
|
+
|
80
|
+
# get! post! put! patch! delete! return an Response when successful, but raise an Error otherwise
|
81
|
+
def define_bang_methods
|
82
|
+
{
|
83
|
+
get!: :get,
|
84
|
+
post!: :post,
|
85
|
+
put!: :put,
|
86
|
+
patch!: :patch,
|
87
|
+
delete!: :delete,
|
88
|
+
}.each do |method_name, implemented_method|
|
89
|
+
self.class.send(:define_method, method_name) do |endpoint, options = {}|
|
90
|
+
result = public_send(implemented_method, endpoint, options)
|
91
|
+
|
92
|
+
fail result if result.error?
|
93
|
+
|
94
|
+
result
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ChimeraHttpClient
|
2
|
+
class Error < StandardError
|
3
|
+
attr_reader :body, :code, :time, :response
|
4
|
+
alias message body
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
@body = response.body
|
8
|
+
@code = response.code
|
9
|
+
@time = response.options&.fetch(:total_time, nil)
|
10
|
+
@response = response # contains the request
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def error?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def parsed_body
|
19
|
+
JSON.parse(body)
|
20
|
+
rescue JSON::ParserError
|
21
|
+
{ "non_json_body" => body }
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
error = "#{self.class.name} (#{code}) #{message}, URL: #{url}"
|
26
|
+
|
27
|
+
return "#{error}, Request: #{response.request.inspect}" if log_requests?
|
28
|
+
|
29
|
+
error
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_json
|
33
|
+
error = {
|
34
|
+
code: code,
|
35
|
+
error_class: self.class.name,
|
36
|
+
method: http_method,
|
37
|
+
url: url,
|
38
|
+
message: message,
|
39
|
+
}
|
40
|
+
|
41
|
+
return error.merge({ request: response.request.inspect }).to_json if log_requests?
|
42
|
+
|
43
|
+
error.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def url
|
49
|
+
response.request.base_url
|
50
|
+
end
|
51
|
+
|
52
|
+
def http_method
|
53
|
+
response.request.options[:method]
|
54
|
+
end
|
55
|
+
|
56
|
+
def log_requests?
|
57
|
+
ENV["CHIMERA_HTTP_CLIENT_LOG_REQUESTS"] == true || ENV["CHIMERA_HTTP_CLIENT_LOG_REQUESTS"] == "true"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class ParameterMissingError < StandardError; end # missing parameters
|
62
|
+
class JsonParserError < StandardError; end # body is not parsable json
|
63
|
+
|
64
|
+
class ConnectionError < Error; end # 0
|
65
|
+
class RedirectionError < Error; end # 301, 302, 303, 307
|
66
|
+
class BadRequestError < Error; end # 400
|
67
|
+
class UnauthorizedError < Error; end # 401
|
68
|
+
class ForbiddenError < Error; end # 403
|
69
|
+
class NotFoundError < Error; end # 404
|
70
|
+
class MethodNotAllowedError < Error; end # 405
|
71
|
+
class ResourceConflictError < Error; end # 409
|
72
|
+
class UnprocessableEntityError < Error; end # 422
|
73
|
+
class ClientError < Error; end # 400..499
|
74
|
+
class ServerError < Error; end # 500..599
|
75
|
+
class TimeoutError < Error; end # timeout
|
76
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ChimeraHttpClient
|
2
|
+
class Request
|
3
|
+
TIMEOUT_SECONDS = 5
|
4
|
+
|
5
|
+
def run(url:, method:, body: nil, options: {}, headers: {})
|
6
|
+
request_params = {
|
7
|
+
method: method,
|
8
|
+
body: body,
|
9
|
+
params: options.fetch(:params, {}),
|
10
|
+
headers: headers,
|
11
|
+
timeout: options.fetch(:timeout, TIMEOUT_SECONDS),
|
12
|
+
accept_encoding: "gzip",
|
13
|
+
}
|
14
|
+
|
15
|
+
# Basic-auth support:
|
16
|
+
username = options.fetch(:username, nil)
|
17
|
+
password = options.fetch(:password, nil)
|
18
|
+
request_params[:userpwd] = "#{username}:#{password}" if username && password
|
19
|
+
|
20
|
+
request = Typhoeus::Request.new(url, request_params)
|
21
|
+
|
22
|
+
result = nil
|
23
|
+
request.on_complete do |response|
|
24
|
+
result = on_complete_handler(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
request.run
|
28
|
+
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def on_complete_handler(response)
|
35
|
+
return Response.new(response) if response.success?
|
36
|
+
|
37
|
+
exception_for(response)
|
38
|
+
end
|
39
|
+
|
40
|
+
def exception_for(response)
|
41
|
+
return TimeoutError.new(response) if response.timed_out?
|
42
|
+
|
43
|
+
case response.code.to_i
|
44
|
+
when 301, 302, 303, 307
|
45
|
+
RedirectionError.new(response)
|
46
|
+
when 200..399
|
47
|
+
nil
|
48
|
+
when 400
|
49
|
+
BadRequestError.new(response)
|
50
|
+
when 401
|
51
|
+
UnauthorizedError.new(response)
|
52
|
+
when 403
|
53
|
+
ForbiddenError.new(response)
|
54
|
+
when 404
|
55
|
+
NotFoundError.new(response)
|
56
|
+
when 405
|
57
|
+
MethodNotAllowedError.new(response)
|
58
|
+
when 409
|
59
|
+
ResourceConflictError.new(response)
|
60
|
+
when 422
|
61
|
+
UnprocessableEntityError.new(response)
|
62
|
+
when 400..499
|
63
|
+
ClientError.new(response)
|
64
|
+
when 500..599
|
65
|
+
ServerError.new(response)
|
66
|
+
else # response.code.zero?
|
67
|
+
ConnectionError.new(response)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ChimeraHttpClient
|
2
|
+
class Response
|
3
|
+
attr_reader :body, :code, :time, :response
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
@body = response.body
|
7
|
+
@code = response.code
|
8
|
+
@time = response.total_time
|
9
|
+
@response = response # contains the request
|
10
|
+
end
|
11
|
+
|
12
|
+
def parsed_body
|
13
|
+
JSON.parse(body)
|
14
|
+
rescue JSON::ParserError => e
|
15
|
+
raise ChimeraHttpClient::JsonParserError, "Could not parse body as JSON: #{body}, error: #{e.message}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def error?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chimera_http_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andreas Finger
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: typhoeus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: irb
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.50'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.50'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.4'
|
111
|
+
description: General http client functionality to easily connect to JSON endpoints
|
112
|
+
email:
|
113
|
+
- webmaster@mediafinger.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
121
|
+
- ".ruby-version"
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE
|
124
|
+
- README.markdown
|
125
|
+
- Rakefile
|
126
|
+
- TODO.markdown
|
127
|
+
- chimera_http_client.gemspec
|
128
|
+
- lib/chimera_http_client.rb
|
129
|
+
- lib/chimera_http_client/connection.rb
|
130
|
+
- lib/chimera_http_client/error.rb
|
131
|
+
- lib/chimera_http_client/request.rb
|
132
|
+
- lib/chimera_http_client/response.rb
|
133
|
+
- lib/chimera_http_client/version.rb
|
134
|
+
homepage: https://github.com/mediafinger/chimera_http_client
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubygems_version: 3.0.3
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: Http Client to connect to REST APIs
|
157
|
+
test_files: []
|