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 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
@@ -0,0 +1,4 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /doc/
4
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
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
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in chimera_http_client.gemspec
4
+ gemspec
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,8 @@
1
+ module ChimeraHttpClient
2
+ ROOT_PATH = File.expand_path("..", __dir__)
3
+
4
+ require "json"
5
+ require "typhoeus"
6
+
7
+ Dir.glob(ROOT_PATH + "/lib/chimera_http_client/*.rb") { |file| require file }
8
+ 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
@@ -0,0 +1,3 @@
1
+ module ChimeraHttpClient
2
+ VERSION = "0.2.0".freeze
3
+ 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: []