chimera_http_client 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/README.markdown +183 -19
- data/Rakefile +6 -1
- data/TODO.markdown +34 -16
- data/chimera_http_client.gemspec +1 -0
- data/lib/chimera_http_client/base.rb +54 -0
- data/lib/chimera_http_client/connection.rb +9 -56
- data/lib/chimera_http_client/queue.rb +49 -0
- data/lib/chimera_http_client/request.rb +15 -6
- data/lib/chimera_http_client/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e2fa666cf4ec3fe21e1f4a8ecfb3932c5dac1cb6d0c7d16762eea73bf76fb44
|
4
|
+
data.tar.gz: df72864e42844dd77dcc9f64bfc23f69d73a66ee66f7589c8ef1e460524b831c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52cd8916ab3bd1956753eac56a54a0c9b6130fdfbb38e9d7f2083b137097e1a585a8d205ad75f6e3b6713ee55cf66c1443b8136c22f7d48c8e0045da397612c5
|
7
|
+
data.tar.gz: 70f7a691115d59909ec1831d48ed1fc02f4aa7ce8057bc35cf015a4254ee1f09318b45a20cf30686548d2b0bd686644ac0df6f5f6e9e9c2303b09b5cedfbb611
|
data/.rubocop.yml
CHANGED
@@ -76,6 +76,9 @@ Style/NumericLiterals:
|
|
76
76
|
Style/Semicolon:
|
77
77
|
AllowAsExpressionSeparator: true
|
78
78
|
|
79
|
+
Style/SymbolProc:
|
80
|
+
Enabled: false
|
81
|
+
|
79
82
|
# Cop supports --auto-correct.
|
80
83
|
# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
|
81
84
|
# SupportedStyles: comma, consistent_comma, no_comma
|
data/README.markdown
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# ChimeraHttpClient
|
2
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
|
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 comfortable and unifying way** to access endpoints from other apps.
|
4
|
+
|
5
|
+
And what works for the internal communication between your own apps, will also work for external APIs that do not offer a client for simplified access.
|
6
|
+
|
7
|
+
It offers an **easy to learn interface** and **nice error handling**. And it enables you to **queue HTTP requests to run them in parallel** for better performance.
|
4
8
|
|
5
9
|
## Dependencies
|
6
10
|
|
@@ -8,10 +12,43 @@ The `chimera_http_client` gem is using the _libcurl_ wrapper [**Typhoeus**](http
|
|
8
12
|
|
9
13
|
It does not have any other runtime dependencies.
|
10
14
|
|
11
|
-
###
|
15
|
+
### ENV variables
|
12
16
|
|
13
17
|
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
18
|
|
19
|
+
## Table of Contents
|
20
|
+
|
21
|
+
<!-- TOC depthFrom:1 depthTo:4 withLinks:1 updateOnSave:0 orderedList:0 -->
|
22
|
+
|
23
|
+
- [ChimeraHttpClient](#chimerahttpclient)
|
24
|
+
- [Table of Contents](#table-of-contents)
|
25
|
+
- [Dependencies](#dependencies)
|
26
|
+
- [ENV variables](#env-variables)
|
27
|
+
- [The Connection class](#the-connection-class)
|
28
|
+
- [Initialization](#initialization)
|
29
|
+
- [Mandatory initialization parameter `base_url`](#mandatory-initialization-parameter-baseurl)
|
30
|
+
- [Optional initialization parameters](#optional-initialization-parameters)
|
31
|
+
- [Request methods](#request-methods)
|
32
|
+
- [Mandatory request parameter `endpoint`](#mandatory-request-parameter-endpoint)
|
33
|
+
- [Optional request parameters](#optional-request-parameters)
|
34
|
+
- [Basic auth](#basic-auth)
|
35
|
+
- [Timeout duration](#timeout-duration)
|
36
|
+
- [Custom logger](#custom-logger)
|
37
|
+
- [Caching responses](#caching-responses)
|
38
|
+
- [Example usage](#example-usage)
|
39
|
+
- [The Request class](#the-request-class)
|
40
|
+
- [The Response class](#the-response-class)
|
41
|
+
- [Error classes](#error-classes)
|
42
|
+
- [The Queue class](#the-queue-class)
|
43
|
+
- [Queueing requests](#queueing-requests)
|
44
|
+
- [Executing requests in parallel](#executing-requests-in-parallel)
|
45
|
+
- [Installation](#installation)
|
46
|
+
- [Maintainers and Contributors](#maintainers-and-contributors)
|
47
|
+
- [Roadmap](#roadmap)
|
48
|
+
- [Chimera](#chimera)
|
49
|
+
|
50
|
+
<!-- /TOC -->
|
51
|
+
|
15
52
|
## The Connection class
|
16
53
|
|
17
54
|
The basic usage looks like this:
|
@@ -21,38 +58,119 @@ connection = ChimeraHttpClient::Connection.new(base_url: 'http://localhost/names
|
|
21
58
|
response = connection.get!(endpoint, params: params)
|
22
59
|
```
|
23
60
|
|
24
|
-
|
25
|
-
|
61
|
+
### Initialization
|
62
|
+
|
63
|
+
`connection = ChimeraHttpClient::Connection.new(base_url: 'http://localhost:3000/v1', logger: logger, cache: cache)`
|
64
|
+
|
65
|
+
#### Mandatory initialization parameter `base_url`
|
66
|
+
|
67
|
+
The mandatory parameter is **base_url** which should include the host, port and base path to the API endpoints you want to call, e.g. `'http://localhost:3000/v1'`.
|
68
|
+
|
69
|
+
Setting the `base_url` is meant to be a comfort feature, as you can then pass short endpoints to each request like `/users`. You could set an empty string `''` as `base_url` and then pass full qualified URLs as endpoint of the requests.
|
70
|
+
|
71
|
+
#### Optional initialization parameters
|
26
72
|
|
27
|
-
|
28
|
-
`connection.get!("users/#{id}")`
|
29
|
-
or
|
30
|
-
`connection.get(['users', id], options: { headers: { ' Accept-Charset' => 'utf-8' })`
|
73
|
+
The optional parameters are:
|
31
74
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
75
|
+
* `logger` - an instance of a logger class that implements `#info` and `#warn` methods
|
76
|
+
* `timeout` - the timeout for all requests, can be overwritten in any request, the default are 3 seconds
|
77
|
+
* `user_agent` - if you would like your calls to identify with a specific user agent
|
78
|
+
* `verbose` - the default is `false`, set it to true while debugging issues
|
79
|
+
* `cache` - an instance of your cache solution, can be overwritten in any request
|
36
80
|
|
37
|
-
###
|
81
|
+
### Request methods
|
82
|
+
|
83
|
+
The available methods are:
|
84
|
+
|
85
|
+
* `get` / `get!`
|
86
|
+
* `post` / `post!`
|
87
|
+
* `put` / `put`
|
88
|
+
* `patch` / `patch!`
|
89
|
+
* `delete` / `delete!`
|
90
|
+
|
91
|
+
where the methods ending on a _bang!_ will raise an error (which you should handle in your application) while the others will return an error object.
|
92
|
+
|
93
|
+
#### Mandatory request parameter `endpoint`
|
94
|
+
|
95
|
+
The `base_url` set in the connection will together with the `endpoint` determine the URL to make a request to.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
connection.get([:users, id])
|
99
|
+
connection.get(["users", id])
|
100
|
+
connection.get("users/#{id}")
|
101
|
+
connection.get("/users/#{id}")
|
102
|
+
```
|
38
103
|
|
39
|
-
|
40
|
-
`options: { username: 'admin', password: 'secret' }`
|
104
|
+
All forms above ave valid and will make a request to the same URL.
|
41
105
|
|
42
|
-
|
106
|
+
* Please take note that _the endpoint can be given as a String, a Symbol, or an Array._
|
107
|
+
* While they do no harm, there is _no need to pass leading or trailing `/` in endpoints._
|
108
|
+
* When passing the endpoint as an Array, _it's elements are converted to Strings and concatenated with `/`._
|
109
|
+
|
110
|
+
#### Optional request parameters
|
111
|
+
|
112
|
+
All request methods expect a mandatory `endpoint` and an optional hash as parameters. In the latter the following keywords are treated specially:
|
113
|
+
|
114
|
+
* `body` - the mandatory body of a `post`, `put` or `patch` request
|
115
|
+
* `headers` - a hash of HTTP headers
|
116
|
+
* `params` - parameters of a HTTP request
|
117
|
+
* `username` - used for a BasicAuth login
|
118
|
+
* `password` - used for a BasicAuth login
|
119
|
+
* `timeout` - set a custom timeout per request (the default is 3 seconds)
|
120
|
+
* `cache` - optionally overwrite the cache store set in `Connection` in any request
|
121
|
+
|
122
|
+
Example:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
connection.post(
|
126
|
+
:users,
|
127
|
+
body: { name: "Andy" },
|
128
|
+
params: { origin: `Twitter`},
|
129
|
+
headers: { "Authorization" => "Bearer #{token}" },
|
130
|
+
timeout: 10,
|
131
|
+
cache: nil
|
132
|
+
)
|
133
|
+
```
|
134
|
+
|
135
|
+
#### Basic auth
|
136
|
+
|
137
|
+
In case you need to use an API that is protected by **basic_auth** just pass the credentials as optional parameters:
|
138
|
+
`username: 'admin', password: 'secret'`
|
139
|
+
|
140
|
+
#### Timeout duration
|
43
141
|
|
44
142
|
The default timeout duration is **3 seconds**.
|
45
143
|
|
46
144
|
If you want to use a different timeout, you can pass the key `timeout` when initializing the `Connection`. You can also overwrite it on every call.
|
47
145
|
|
48
|
-
|
146
|
+
#### Custom logger
|
49
147
|
|
50
148
|
By default no logging is happening. If you need request logging, you can pass your custom Logger to the key `logger` when initializing the `Connection`. It will write to `logger.info` when starting and when completing a request.
|
51
149
|
|
52
|
-
|
150
|
+
#### Caching responses
|
53
151
|
|
54
152
|
To cache all the reponses of a connection, just pass the optional parameter `cache` to its initializer. You can also overwrite the connection's cache configuration by passing the parameter `cache` to any `get` call.
|
55
153
|
|
154
|
+
It could be an instance of an implementation as simple as this:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
class Cache
|
158
|
+
def initialize
|
159
|
+
@memory = {}
|
160
|
+
end
|
161
|
+
|
162
|
+
def get(request)
|
163
|
+
@memory[request]
|
164
|
+
end
|
165
|
+
|
166
|
+
def set(request, response)
|
167
|
+
@memory[request] = response
|
168
|
+
end
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
Or use an adapter for Dalli, Redis, or Rails cache that also support an optional time-to-live `default_ttl` parameter.
|
173
|
+
|
56
174
|
Read more about how to use it: https://github.com/typhoeus/typhoeus#caching
|
57
175
|
|
58
176
|
### Example usage
|
@@ -180,6 +298,52 @@ The error classes and their corresponding http error codes:
|
|
180
298
|
ServerError # 500..599
|
181
299
|
TimeoutError # timeout
|
182
300
|
|
301
|
+
## The Queue class
|
302
|
+
|
303
|
+
Instead of making single requests immediately, the ChimeraHttpClient allows to queue requests and run them in **parallel**.
|
304
|
+
|
305
|
+
The number of parallel requests is limited by your system. There is a hard limit for 200 concurrent requests. You will have to measure yourself where the sweet spot for optimal performance is - and when things start to get flaky. I recommend to queue not much more than 20 requests before running them.
|
306
|
+
|
307
|
+
### Queueing requests
|
308
|
+
|
309
|
+
The initializer of the `Queue` class expects and handles the same parameters as the `Connection` class.
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
queue = ChimeraHttpClient::Queue.new(base_url: 'http://localhost:3000/v1')
|
313
|
+
```
|
314
|
+
|
315
|
+
`queue.add` expects and handles the same parameters as the requests methods of a connection.
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
queue.add(method, endpoint, options = {})
|
319
|
+
```
|
320
|
+
|
321
|
+
The only difference is that a parameter to set the HTTP method has to prepended. Valid options for `method` are:
|
322
|
+
|
323
|
+
* `:get` / `'get'` / `'GET'`
|
324
|
+
* `:post` / `'post'` / `'POST'`
|
325
|
+
* `:put` / `'put'` / `'PUT'`
|
326
|
+
* `:patch` / `'patch'` / `'PATCH'`
|
327
|
+
* `:delete` / `'delete'` / `'DELETE'`
|
328
|
+
|
329
|
+
### Executing requests in parallel
|
330
|
+
|
331
|
+
Once the queue is filled, run all the requests concurrently with:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
responses = queue.execute
|
335
|
+
```
|
336
|
+
|
337
|
+
`responses` will contain an Array of `ChimeraHttpClient::Response` objects when all calls succeed. If any calls fail, the Array will also contain `ChimeraHttpClient::Error` objects. It is in your responsibility to handle the errors.
|
338
|
+
|
339
|
+
> Tip: every `Response` and every `Error` make the underlying `Typheous::Request` available over `object.response.request`, which could help with debugging, or with building your own retry mechanism.
|
340
|
+
|
341
|
+
### Empty the queue
|
342
|
+
|
343
|
+
The queue is emptied after execution. You could also empty it at any other point before by calling `queue.empty`.
|
344
|
+
|
345
|
+
To inspect the requests waiting for execution, call `queue.queued_requests`.
|
346
|
+
|
183
347
|
## Installation
|
184
348
|
|
185
349
|
Add this line to your application's Gemfile:
|
@@ -196,7 +360,7 @@ When updating the version, do not forget to run
|
|
196
360
|
|
197
361
|
## Maintainers and Contributors
|
198
362
|
|
199
|
-
After checking out the repo, run `rake` to run the **tests and rubocop**.
|
363
|
+
After checking out the repo, run `bundle install` and then `bundle execute rake` to run the **tests and rubocop**.
|
200
364
|
|
201
365
|
You can also run `rake console` to open an irb session with the `ChimeraHttpClient` pre-loaded that will allow you to experiment.
|
202
366
|
|
data/Rakefile
CHANGED
@@ -5,7 +5,12 @@ RSpec::Core::RakeTask.new(:rspec)
|
|
5
5
|
|
6
6
|
desc "Open a console with the ChimeraHttpClient loaded"
|
7
7
|
task :console do
|
8
|
-
|
8
|
+
puts "Console with the gem and awesome_print loaded:"
|
9
|
+
ARGV.clear
|
10
|
+
require "irb"
|
11
|
+
require "ap"
|
12
|
+
load "lib/chimera_http_client.rb"
|
13
|
+
IRB.start
|
9
14
|
end
|
10
15
|
|
11
16
|
desc "Run Rubocop"
|
data/TODO.markdown
CHANGED
@@ -15,11 +15,11 @@ _none known_
|
|
15
15
|
|
16
16
|
### Logger
|
17
17
|
|
18
|
-
*
|
19
|
-
*
|
20
|
-
*
|
21
|
-
*
|
22
|
-
*
|
18
|
+
* ~~allow to pass a logger~~
|
19
|
+
* ~~add logger.info when starting a http call~~
|
20
|
+
* ~~add logger.info when finishing a successful http call~~
|
21
|
+
* ~~include the total_time of the requests in the log~~
|
22
|
+
* ~~add (example) to README~~
|
23
23
|
* add logger.warn / .error for error cases (?)
|
24
24
|
|
25
25
|
### Custom Serializer
|
@@ -35,28 +35,46 @@ _none known_
|
|
35
35
|
* use custom deserializer in #parsed_body instead of default JSON parsing
|
36
36
|
* add example to README
|
37
37
|
|
38
|
-
### Queueing
|
38
|
+
### Queueing / running in parallel
|
39
39
|
|
40
|
-
* allow to queue multiple
|
41
|
-
* execute (up to
|
40
|
+
* ~~allow to queue multiple requests~~
|
41
|
+
* ~~execute (up to 200) requests in parallel~~
|
42
|
+
* allow to pass one proc / block (for all requests) to use as on_complete handler for each request
|
42
43
|
* add example to README
|
43
44
|
|
44
|
-
###
|
45
|
+
### Queueing / run requests serialized
|
45
46
|
|
46
|
-
*
|
47
|
+
* allow to queue multiple requests
|
48
|
+
* execute (up to 5) queued requests
|
49
|
+
* allow to pass a proc (per request) to use the response for the next request
|
50
|
+
* or just explain how to code that yourself?
|
47
51
|
* add example to README
|
48
52
|
|
53
|
+
### Caching
|
54
|
+
|
55
|
+
* ~~optional per connection or call~~
|
56
|
+
* ~~add example to README~~
|
57
|
+
|
49
58
|
### Timeout
|
50
59
|
|
51
|
-
*
|
52
|
-
*
|
53
|
-
*
|
60
|
+
* ~~allow to set custom timeout per connection~~
|
61
|
+
* ~~allow to set custom timeout per call~~
|
62
|
+
* ~~add (example) to README~~
|
54
63
|
|
55
64
|
### Release
|
56
65
|
|
57
|
-
*
|
58
|
-
*
|
59
|
-
*
|
66
|
+
* ~~rename module to have unique namespace~~
|
67
|
+
* ~~release to rubygems to add to the plethora of similar gems~~
|
68
|
+
* ~~make repo public~~
|
60
69
|
* hook up Travis-CI
|
61
70
|
* ensure it runs with Ruby 2.4 and newer
|
62
71
|
* get feedback
|
72
|
+
|
73
|
+
### File Uploads
|
74
|
+
|
75
|
+
* add example to README
|
76
|
+
|
77
|
+
### Streaming response bodies
|
78
|
+
|
79
|
+
* enable to pass on_headers, on_body, on_complete procs
|
80
|
+
* add example to README
|
data/chimera_http_client.gemspec
CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency "typhoeus", "~> 1.1"
|
22
22
|
|
23
|
+
spec.add_development_dependency "awesome_print", "~> 1.8"
|
23
24
|
spec.add_development_dependency "bundler", "~> 1.13"
|
24
25
|
spec.add_development_dependency "irb", "~> 1.0"
|
25
26
|
spec.add_development_dependency "rake", ">= 10.0"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ChimeraHttpClient
|
2
|
+
class Base
|
3
|
+
USER_AGENT = "ChimeraHttpClient (by mediafinger)".freeze
|
4
|
+
|
5
|
+
def initialize(base_url:, logger: nil, timeout: nil, user_agent: USER_AGENT, verbose: false, cache: nil)
|
6
|
+
fail(ChimeraHttpClient::ParameterMissingError, "base_url expected, but not given") if base_url.nil?
|
7
|
+
|
8
|
+
@base_url = base_url
|
9
|
+
@logger = logger
|
10
|
+
@timeout = timeout
|
11
|
+
|
12
|
+
Typhoeus::Config.user_agent = user_agent
|
13
|
+
Typhoeus::Config.verbose = verbose
|
14
|
+
Typhoeus::Config.memoize = false
|
15
|
+
Typhoeus::Config.cache = cache
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Add default values to call options
|
21
|
+
def augmented_options(options)
|
22
|
+
options[:timeout] ||= @timeout
|
23
|
+
|
24
|
+
options
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract_body(options)
|
28
|
+
body = options.delete(:body)
|
29
|
+
body_optional = options.delete(:body_optional)
|
30
|
+
fail(ChimeraHttpClient::ParameterMissingError, "body expected, but not given") if body.nil? && !body_optional
|
31
|
+
body
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_headers(options, headers)
|
35
|
+
given_headers = options.delete(:headers) || {}
|
36
|
+
headers.merge(given_headers)
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_headers
|
40
|
+
{ "Content-Type" => "application/json" }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Build URL out of @base_url and endpoint given as String or Array, while trimming redundant "/"
|
44
|
+
def url(endpoint)
|
45
|
+
trimmed_endpoint = Array(endpoint).map { |e| trim(e) }
|
46
|
+
[@base_url.chomp("/"), trimmed_endpoint].flatten.reject(&:empty?).join("/")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Remove leading and trailing "/" from a give part of a String (usually URL or endpoint)
|
50
|
+
def trim(element)
|
51
|
+
element.to_s.sub(%r{^\/}, "").chomp("/")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -2,22 +2,11 @@
|
|
2
2
|
# The bang methods get!, post!, put!, patch! and delete! raise a Error in case of failure
|
3
3
|
|
4
4
|
module ChimeraHttpClient
|
5
|
-
class Connection
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(base_url:, logger: nil, timeout: nil, user_agent: USER_AGENT, verbose: false, cache: nil)
|
9
|
-
fail(ChimeraHttpClient::ParameterMissingError, "base_url expected, but not given") if base_url.nil?
|
10
|
-
|
11
|
-
@base_url = base_url
|
12
|
-
@logger = logger
|
13
|
-
@timeout = timeout
|
5
|
+
class Connection < Base
|
6
|
+
def initialize(options = {})
|
7
|
+
super(options)
|
14
8
|
|
15
9
|
define_bang_methods
|
16
|
-
|
17
|
-
Typhoeus::Config.user_agent = user_agent
|
18
|
-
Typhoeus::Config.verbose = verbose
|
19
|
-
Typhoeus::Config.memoize = false
|
20
|
-
Typhoeus::Config.cache = cache
|
21
10
|
end
|
22
11
|
|
23
12
|
def request
|
@@ -25,70 +14,34 @@ module ChimeraHttpClient
|
|
25
14
|
end
|
26
15
|
|
27
16
|
def get(endpoint, options = {})
|
28
|
-
|
29
|
-
|
30
|
-
request.run(url: url(endpoint), method: :get, options: augmented_options(options), headers: headers)
|
17
|
+
run(:get, endpoint, options.merge(body_optional: true))
|
31
18
|
end
|
32
19
|
|
33
20
|
def post(endpoint, options = {})
|
34
|
-
|
21
|
+
run(:post, endpoint, options)
|
35
22
|
end
|
36
23
|
|
37
24
|
def put(endpoint, options = {})
|
38
|
-
|
25
|
+
run(:put, endpoint, options)
|
39
26
|
end
|
40
27
|
|
41
28
|
def patch(endpoint, options = {})
|
42
|
-
|
29
|
+
run(:patch, endpoint, options)
|
43
30
|
end
|
44
31
|
|
45
32
|
def delete(endpoint, options = {})
|
46
|
-
|
33
|
+
run(:delete, endpoint, options.merge(body_optional: true))
|
47
34
|
end
|
48
35
|
|
49
36
|
private
|
50
37
|
|
51
|
-
|
52
|
-
def augmented_options(options)
|
53
|
-
options[:timeout] ||= @timeout
|
54
|
-
|
55
|
-
options
|
56
|
-
end
|
57
|
-
|
58
|
-
def run_with_body(method, endpoint, options = {})
|
38
|
+
def run(method, endpoint, options = {})
|
59
39
|
body = extract_body(options)
|
60
40
|
headers = extract_headers(options, default_headers)
|
61
41
|
|
62
42
|
request.run(url: url(endpoint), method: method, body: body, options: augmented_options(options), headers: headers)
|
63
43
|
end
|
64
44
|
|
65
|
-
# Build URL out of @base_url and endpoint given as String or Array, while trimming redundant "/"
|
66
|
-
def url(endpoint)
|
67
|
-
trimmed_endpoint = Array(endpoint).map { |e| trim(e) }
|
68
|
-
[@base_url.chomp("/"), trimmed_endpoint].flatten.reject(&:empty?).join("/")
|
69
|
-
end
|
70
|
-
|
71
|
-
# Remove leading and trailing "/" from a give part of a String (usually URL or endpoint)
|
72
|
-
def trim(element)
|
73
|
-
element.to_s.sub(%r{^\/}, "").chomp("/")
|
74
|
-
end
|
75
|
-
|
76
|
-
def extract_body(options)
|
77
|
-
body = options.delete(:body)
|
78
|
-
body_optional = options.delete(:body_optional)
|
79
|
-
fail(ChimeraHttpClient::ParameterMissingError, "body expected, but not given") if body.nil? && !body_optional
|
80
|
-
body
|
81
|
-
end
|
82
|
-
|
83
|
-
def extract_headers(options, headers)
|
84
|
-
given_headers = options.delete(:headers) || {}
|
85
|
-
headers.merge(given_headers)
|
86
|
-
end
|
87
|
-
|
88
|
-
def default_headers
|
89
|
-
{ "Content-Type" => "application/json" }
|
90
|
-
end
|
91
|
-
|
92
45
|
# get! post! put! patch! delete! return an Response when successful, but raise an Error otherwise
|
93
46
|
def define_bang_methods
|
94
47
|
{
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ChimeraHttpClient
|
2
|
+
class Queue < Base
|
3
|
+
def add(method, endpoint, options = {})
|
4
|
+
http_method = method.downcase.to_sym
|
5
|
+
options[:body_optional] = true if %i[get delete].include?(http_method)
|
6
|
+
|
7
|
+
body = extract_body(options)
|
8
|
+
headers = extract_headers(options, default_headers)
|
9
|
+
|
10
|
+
req = Request.new(logger: @logger).create(
|
11
|
+
url: url(endpoint),
|
12
|
+
method: http_method,
|
13
|
+
body: body,
|
14
|
+
options: augmented_options(options),
|
15
|
+
headers: headers
|
16
|
+
)
|
17
|
+
|
18
|
+
queued_requests << req
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute
|
22
|
+
queued_requests.each do |request|
|
23
|
+
hydra.queue(request.request)
|
24
|
+
end
|
25
|
+
|
26
|
+
hydra.run
|
27
|
+
|
28
|
+
responses = queued_requests.map { |request| request.result }
|
29
|
+
|
30
|
+
empty
|
31
|
+
|
32
|
+
responses
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty
|
36
|
+
@queued_requests = []
|
37
|
+
end
|
38
|
+
|
39
|
+
def queued_requests
|
40
|
+
@queued_requests ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def hydra
|
46
|
+
@hydra ||= Typhoeus::Hydra.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -2,11 +2,21 @@ module ChimeraHttpClient
|
|
2
2
|
class Request
|
3
3
|
TIMEOUT_SECONDS = 3
|
4
4
|
|
5
|
+
attr_reader :request, :result
|
6
|
+
|
5
7
|
def initialize(logger: nil)
|
6
8
|
@logger = logger
|
7
9
|
end
|
8
10
|
|
9
11
|
def run(url:, method:, body: nil, options: {}, headers: {})
|
12
|
+
create(url: url, method: method, body: body, options: options, headers: headers)
|
13
|
+
|
14
|
+
@request.run
|
15
|
+
|
16
|
+
@result
|
17
|
+
end
|
18
|
+
|
19
|
+
def create(url:, method:, body: nil, options: {}, headers: {})
|
10
20
|
request_params = {
|
11
21
|
method: method,
|
12
22
|
body: body,
|
@@ -22,20 +32,19 @@ module ChimeraHttpClient
|
|
22
32
|
password = options.fetch(:password, nil)
|
23
33
|
request_params[:userpwd] = "#{username}:#{password}" if username && password
|
24
34
|
|
25
|
-
request = Typhoeus::Request.new(url, request_params)
|
35
|
+
@request = Typhoeus::Request.new(url, request_params)
|
26
36
|
|
27
|
-
result = nil
|
28
|
-
request.on_complete do |response|
|
37
|
+
@result = nil
|
38
|
+
@request.on_complete do |response|
|
29
39
|
@logger&.info("Completed HTTP request: #{method.upcase} #{url} " \
|
30
40
|
"in #{response.total_time&.round(3)}sec with status code #{response.code}")
|
31
41
|
|
32
|
-
result = on_complete_handler(response)
|
42
|
+
@result = on_complete_handler(response)
|
33
43
|
end
|
34
44
|
|
35
45
|
@logger&.info("Starting HTTP request: #{method.upcase} #{url}")
|
36
|
-
request.run
|
37
46
|
|
38
|
-
|
47
|
+
self
|
39
48
|
end
|
40
49
|
|
41
50
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chimera_http_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Finger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-05-
|
11
|
+
date: 2019-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: typhoeus
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: awesome_print
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.8'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,8 +140,10 @@ files:
|
|
126
140
|
- TODO.markdown
|
127
141
|
- chimera_http_client.gemspec
|
128
142
|
- lib/chimera_http_client.rb
|
143
|
+
- lib/chimera_http_client/base.rb
|
129
144
|
- lib/chimera_http_client/connection.rb
|
130
145
|
- lib/chimera_http_client/error.rb
|
146
|
+
- lib/chimera_http_client/queue.rb
|
131
147
|
- lib/chimera_http_client/request.rb
|
132
148
|
- lib/chimera_http_client/response.rb
|
133
149
|
- lib/chimera_http_client/version.rb
|