chimera_http_client 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|