restful_client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +135 -0
- data/lib/restful_client/version.rb +3 -0
- data/lib/restful_client.rb +211 -0
- data/lib/restful_client_configuration.rb +55 -0
- data/lib/restful_client_logger.rb +23 -0
- data/lib/restful_client_uri.rb +11 -0
- data/restful_client.gemspec +26 -0
- data/spec/config/restful_services.yml +22 -0
- data/spec/restful_client_spec.rb +394 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/test_server.rb +33 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fdf61437db7d8ba44585cc4b91bcb2362227c068
|
4
|
+
data.tar.gz: 7d9fc6320b56785f07249b3146b15a04af2693b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 508b11c0cae03e2a895305162b92737e26e78e8e2f186d0a2b4edf454da5607eb624659022b606ec43eacec0685ea7a8f9e82de241cc3690470a85a766c3e7cb
|
7
|
+
data.tar.gz: c2f023f3df2c3bcedfe4916ddbb5c0597979e8cf310f0f763da9fde6470a31cd86b8c0b5fb9ccbae7b38fdabd5f74da4e26e5e049d107923c34e98711233d744
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
restful_client
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.2
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Avner Cohen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# RestfulClient
|
2
|
+
|
3
|
+
An HTTP framework for micro-services based environment, build on top of [typhoeus](https://github.com/typhoeus/typhoeus) and [servicejynx](https://github.com/AvnerCohen/service-jynx)
|
4
|
+
|
5
|
+
[](https://travis-ci.org/AvnerCohen/restful-client)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
gem 'restful_client'
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
* Clean restful api supporting http verbs (GET/PUT/POST/DELETE)
|
14
|
+
* Manage Failures - set stubs for case of errors and SERVICE_DOWN event from [Jynx](https://github.com/AvnerCohen/service-jynx)
|
15
|
+
* Strcutured and configurable YAML for multiple service end points
|
16
|
+
* Build using Typheous, a fast and robuts http client, built on top of libcurl
|
17
|
+
* Configurable timeouts for http request
|
18
|
+
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
Create the "restful_services.yml" file in your config folder.
|
22
|
+
Configuration for the various service is based on top of YAML that configures the http service endpoints and ServiceJynx setup:
|
23
|
+
|
24
|
+
<pre>
|
25
|
+
development: &development
|
26
|
+
users:
|
27
|
+
url: http://1.2.3.4:7711/api/v1/
|
28
|
+
time_window_in_seconds: 20
|
29
|
+
max_errors: 10
|
30
|
+
grace_period: 60
|
31
|
+
|
32
|
+
production: &production
|
33
|
+
users:
|
34
|
+
url: http://1.2.3.4:7711/api/v0/
|
35
|
+
time_window_in_seconds: 20
|
36
|
+
max_errors: 10
|
37
|
+
grace_period: 60
|
38
|
+
|
39
|
+
</pre>
|
40
|
+
|
41
|
+
### Possible flags
|
42
|
+
|
43
|
+
* use_jynx - Remove the integrated jynx-service protection (default: false)
|
44
|
+
* report_method - proc to be executed in the case of error
|
45
|
+
* env_name - environment name (production|staging|development etc..)
|
46
|
+
* config_folder - path to the configuration folder of the restful_services.yml file.
|
47
|
+
* user_agent - user agent... duh! (users_service|mobile_service|etc..)
|
48
|
+
* legacy_postfix - Legacy version accessed the restful_services.yml with an additional postfix in the yaml.
|
49
|
+
|
50
|
+
## Usage
|
51
|
+
|
52
|
+
In your environment initializer:
|
53
|
+
|
54
|
+
<pre>
|
55
|
+
RestfulClient.configure do |config|
|
56
|
+
config.env_name = Rails.env
|
57
|
+
config.config_folder = "config"
|
58
|
+
end
|
59
|
+
</pre>
|
60
|
+
|
61
|
+
When an error occurs, restful client will report it, as part of the configuration, you an provide it with a reporting hook service, such as graylog or airbrake or whatever you want.
|
62
|
+
|
63
|
+
Data from the report_method will be reported as ```func(klass_name, message, Exception)```
|
64
|
+
|
65
|
+
Consider the following example:
|
66
|
+
|
67
|
+
<pre>
|
68
|
+
#reporting method
|
69
|
+
def report_to_graylog(klass, message, e)
|
70
|
+
Logger.warn "#{klass}::#{message}"
|
71
|
+
$gelf_notifier.send_to_graylog2(e)
|
72
|
+
end
|
73
|
+
|
74
|
+
RestfulClient.configure do |config|
|
75
|
+
config.env_name = ENV['RACK_ENV']
|
76
|
+
config.config_folder = "config"
|
77
|
+
config.user_agent = "my_service"
|
78
|
+
#proc hock to the reporting method
|
79
|
+
config.report_method = proc {|*args| report_to_graylog(*args) }
|
80
|
+
end
|
81
|
+
</pre>
|
82
|
+
|
83
|
+
|
84
|
+
Default timeout for a call is set to 10 seconds, if you want to configure anything different:
|
85
|
+
<pre>
|
86
|
+
|
87
|
+
RestfulClient.configure do |config|
|
88
|
+
config.env_name = Rails.env
|
89
|
+
config.config_folder = "#{Rails.root}/config"
|
90
|
+
config.timeout = 5
|
91
|
+
end
|
92
|
+
|
93
|
+
</pre>
|
94
|
+
|
95
|
+
Than use the service:
|
96
|
+
|
97
|
+
<pre>
|
98
|
+
RestfulClient.get("posts", "/comments/#{user.id}") do
|
99
|
+
[] #default value to be returned on failure
|
100
|
+
end
|
101
|
+
|
102
|
+
#or
|
103
|
+
RestfulClient.delete("posts", {comments: [1,2,4]}, "/comments/#{some_id}") do
|
104
|
+
"ok" #default value to be returned on failure
|
105
|
+
end
|
106
|
+
</pre>
|
107
|
+
|
108
|
+
## Forward IP of client
|
109
|
+
In a complex micro services environment, when services are chained together, you might need to pass along the original IP of the client.
|
110
|
+
Implementation is based on a global $client_ip that can be set and will be assigned to the "X-Forwarded-For" http header.
|
111
|
+
So yeah, no JRuby support at this time.
|
112
|
+
|
113
|
+
|
114
|
+
## Reusing configuration
|
115
|
+
In some cases you might need to use, join or read the base URL of a given service:
|
116
|
+
|
117
|
+
|
118
|
+
Given:
|
119
|
+
|
120
|
+
````
|
121
|
+
users:
|
122
|
+
url: http://1.2.3.4:8383/api/v0/
|
123
|
+
````
|
124
|
+
<pre>
|
125
|
+
|
126
|
+
RestfulClient.srv_url('posts') # ==> http://1.2.3.4:8383/api/v0/
|
127
|
+
</pre>
|
128
|
+
|
129
|
+
## Contributing
|
130
|
+
|
131
|
+
1. Fork it
|
132
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
133
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
134
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
135
|
+
5. Create new Pull Request
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'typhoeus'
|
3
|
+
require 'service_jynx'
|
4
|
+
require 'json'
|
5
|
+
require_relative './restful_client_configuration.rb'
|
6
|
+
require_relative './restful_client_logger.rb'
|
7
|
+
require_relative './restful_client_uri.rb'
|
8
|
+
|
9
|
+
module RestfulClient
|
10
|
+
module_function
|
11
|
+
|
12
|
+
class RestError < StandardError; end
|
13
|
+
|
14
|
+
@@configuration = nil
|
15
|
+
@@logger = nil
|
16
|
+
@@timeout_occured_count = 0
|
17
|
+
|
18
|
+
SERVER_SIDE_ERRORS_RANGE = 500
|
19
|
+
|
20
|
+
def self.configure
|
21
|
+
@@configuration ||= RestfulClientConfiguration.new
|
22
|
+
yield(configuration)
|
23
|
+
@@configuration.run!
|
24
|
+
@@logger = RestfulClientLogger.logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def configuration
|
28
|
+
@@configuration
|
29
|
+
end
|
30
|
+
|
31
|
+
def logger
|
32
|
+
@@logger
|
33
|
+
end
|
34
|
+
|
35
|
+
def srv_url(caller)
|
36
|
+
callerr_config(caller)["url"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def get(caller, path, params = {}, extra_options = {}, &on_error_block)
|
40
|
+
url = RestfulClientUri.uri_join(callerr_config(caller)["url"], path)
|
41
|
+
headers = { "Accept" => "text/json" }
|
42
|
+
headers.merge!(extra_options.fetch("headers", {}))
|
43
|
+
request = Typhoeus::Request.new(url, headers: headers, method: 'GET', timeout: timeout, params: params)
|
44
|
+
run_safe_request(caller, request, true, &on_error_block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def post(caller, path, payload, extra_options = {}, &on_error_block)
|
48
|
+
url = RestfulClientUri.uri_join(callerr_config(caller)["url"], path)
|
49
|
+
headers, payload_as_str = prepare_payload_with_headers(payload, extra_options.fetch("headers", {}))
|
50
|
+
request = Typhoeus::Request.new(url, headers: headers, method: 'POST', body: payload_as_str, timeout: timeout)
|
51
|
+
run_safe_request(caller, request, false, &on_error_block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def post_raw(caller, path, payload, custom_timeout = timeout, &on_error_block)
|
55
|
+
url = RestfulClientUri.uri_join(callerr_config(caller)["url"], path)
|
56
|
+
request = Typhoeus::Request.new(url, method: 'POST', body: payload, timeout: custom_timeout)
|
57
|
+
run_safe_request(caller, request, false, &on_error_block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete(caller, path, payload = {}, extra_options = {}, &on_error_block)
|
61
|
+
url = RestfulClientUri.uri_join(callerr_config(caller)["url"], path)
|
62
|
+
headers, payload_as_str = prepare_payload_with_headers(payload, extra_options.fetch("headers", {}))
|
63
|
+
request = Typhoeus::Request.new(url, headers: headers, method: 'DELETE', body: payload_as_str, timeout: timeout)
|
64
|
+
run_safe_request(caller, request, true, &on_error_block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def put(caller, path, payload, extra_options = {}, &on_error_block)
|
68
|
+
url = RestfulClientUri.uri_join(callerr_config(caller)["url"], path)
|
69
|
+
headers, payload_as_str = prepare_payload_with_headers(payload, extra_options.fetch("headers", {}))
|
70
|
+
request = Typhoeus::Request.new(url, headers: headers, method: 'PUT', body: payload_as_str, timeout: timeout)
|
71
|
+
run_safe_request(caller, request, false, &on_error_block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def callerr_config(caller)
|
75
|
+
caller_setup = configuration.data["#{caller}#{legacy_postfix}"]
|
76
|
+
raise "Couldn't find ['#{caller}#{legacy_postfix}'] in the configuration YAML !!" unless caller_setup
|
77
|
+
caller_setup
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_safe_request(caller, request, retry_if_needed, &on_error_block)
|
81
|
+
|
82
|
+
@@timeout_occured_count = 0
|
83
|
+
if !use_jynx?
|
84
|
+
response = run_request(request.dup, __method__, false)
|
85
|
+
elsif ServiceJynx.alive?(caller)
|
86
|
+
begin
|
87
|
+
response = run_request(request.dup, __method__, retry_if_needed)
|
88
|
+
end while response.is_a?(Typhoeus::Response)
|
89
|
+
|
90
|
+
response
|
91
|
+
else
|
92
|
+
response = on_error_block.call("#{caller} set as down.") if on_error_block
|
93
|
+
end
|
94
|
+
response
|
95
|
+
rescue => e
|
96
|
+
res = ServiceJynx.failure!(caller) if use_jynx?
|
97
|
+
report_method.call("ServiceJynx", "Service #{caller} was taken down as a result of exception", e) if res == :WENT_DOWN
|
98
|
+
on_error_block.call("Exception in #{caller} execution - #{e.message}") if on_error_block
|
99
|
+
end
|
100
|
+
|
101
|
+
def run_request(request, method, retry_if_needed)
|
102
|
+
logger.debug { "#{__method__} :: Request :: #{request.inspect}" }
|
103
|
+
request.options[:headers].merge!({'X-Forwarded-For' => $client_ip}) if $client_ip
|
104
|
+
request.options[:headers].merge!({'User-Agent' => user_agent})
|
105
|
+
request.on_complete do |response|
|
106
|
+
#200, OK
|
107
|
+
if response.success?
|
108
|
+
logger.debug { "Success in #{method} :: Code: #{response.response_code}, #{response.body}" }
|
109
|
+
return "" if response.body.empty?
|
110
|
+
begin
|
111
|
+
return JSON.parse(response.body)
|
112
|
+
rescue => e
|
113
|
+
logger.error { "Response from #{response.effective_url} is not a valid json - [#{response.body}]"}
|
114
|
+
raise e
|
115
|
+
end
|
116
|
+
#Timeout occured
|
117
|
+
elsif response.timed_out?
|
118
|
+
@@timeout_occured_count = @@timeout_occured_count + 1
|
119
|
+
skip_raise = (retry_if_needed && @@timeout_occured_count <= retries)
|
120
|
+
|
121
|
+
error_type = "TimeoutOccured"
|
122
|
+
error_description = prettify_logger(error_type, request, response)
|
123
|
+
logger.error { "Time out in #{method} for: #{error_description}" }
|
124
|
+
|
125
|
+
exception = RuntimeError.new(error_description)
|
126
|
+
exception.set_backtrace(caller)
|
127
|
+
report_method.call("RestError", error_description, exception)
|
128
|
+
|
129
|
+
raise RestError.new(response.return_code.to_sym) unless skip_raise
|
130
|
+
# Could not get an http response, something's wrong.
|
131
|
+
elsif response.code == 0
|
132
|
+
|
133
|
+
error_type = :HttpError
|
134
|
+
error_description = prettify_logger(error_type, request, response)
|
135
|
+
logger.error { "HttpError Error #{response.code}/#{response.return_code} for: #{error_description}" }
|
136
|
+
|
137
|
+
exception = RuntimeError.new(error_description)
|
138
|
+
exception.set_backtrace(caller)
|
139
|
+
report_method.call("RestError", error_description, exception)
|
140
|
+
raise RestError.new(error_type)
|
141
|
+
# Received a non-successful http response.
|
142
|
+
elsif response.code >= SERVER_SIDE_ERRORS_RANGE
|
143
|
+
|
144
|
+
error_type = :BadReturnCode
|
145
|
+
error_description = prettify_logger(error_type, request, response)
|
146
|
+
logger.error { "#{error_type} #{response.code}/#{response.return_code} for: #{error_description}" }
|
147
|
+
|
148
|
+
report_method.call("RestError", error_description, exception)
|
149
|
+
exception = RuntimeError.new(error_description)
|
150
|
+
|
151
|
+
exception.set_backtrace(caller)
|
152
|
+
|
153
|
+
raise RestError.new(error_type)
|
154
|
+
else
|
155
|
+
|
156
|
+
raise RestError.new(:BadReturnCode)
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
request.run
|
161
|
+
end
|
162
|
+
|
163
|
+
def prepare_payload_with_headers(payload, custom_headers)
|
164
|
+
headers = {}
|
165
|
+
if payload.is_a?(Hash)
|
166
|
+
payload_as_str = payload.to_json(root: false)
|
167
|
+
headers.merge!({ "Content-Type" => "application/json" })
|
168
|
+
else
|
169
|
+
payload_as_str = payload
|
170
|
+
end
|
171
|
+
|
172
|
+
headers.merge!(custom_headers)
|
173
|
+
|
174
|
+
[headers, payload_as_str]
|
175
|
+
end
|
176
|
+
|
177
|
+
def fake(caller, path, options = {}, &block)
|
178
|
+
url = RestfulClientUri.uri_join(callerr_config(caller)["url"], path)
|
179
|
+
Typhoeus.stub(url, options = {}, &block)
|
180
|
+
end
|
181
|
+
|
182
|
+
def timeout
|
183
|
+
configuration.timeout
|
184
|
+
end
|
185
|
+
|
186
|
+
def user_agent
|
187
|
+
configuration.user_agent
|
188
|
+
end
|
189
|
+
|
190
|
+
def use_jynx?
|
191
|
+
configuration.use_jynx
|
192
|
+
end
|
193
|
+
|
194
|
+
def legacy_postfix
|
195
|
+
configuration.legacy_postfix
|
196
|
+
end
|
197
|
+
|
198
|
+
def retries
|
199
|
+
configuration.retries
|
200
|
+
end
|
201
|
+
|
202
|
+
def report_method
|
203
|
+
configuration.report_method
|
204
|
+
end
|
205
|
+
|
206
|
+
def prettify_logger(type, request, response)
|
207
|
+
return "#{type} with no request or response." unless request || response
|
208
|
+
"#{type} #{response.code}/#{response.return_code} for: #{request.options.fetch(:method)} #{request.base_url}, Total time: #{response.total_time} seconds"
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class RestfulClientConfiguration
|
4
|
+
attr_accessor :config_folder, :report_method, :data, :env_name, :timeout, :user_agent, :retries, :use_jynx, :legacy_postfix
|
5
|
+
DEFAULT_TIMEOUT = 10
|
6
|
+
DEFAULT_RETRIES = 2
|
7
|
+
DEFAULT_USER_AGENT = 'RestfulClient - https://github.com/AvnerCohen/restful-client'
|
8
|
+
|
9
|
+
def run!
|
10
|
+
raise "Configuration directory name must be provided" unless config_folder.class.to_s == "String"
|
11
|
+
file_name = ["restful_services.yml", "rest_api.yml"].each do |name|
|
12
|
+
locale_name = File.join(config_folder, name)
|
13
|
+
break locale_name if File.file?(locale_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
## Set Default Values
|
17
|
+
@report_method ||= proc {|*args| nil }
|
18
|
+
@timeout ||= DEFAULT_TIMEOUT
|
19
|
+
@user_agent ||= DEFAULT_USER_AGENT
|
20
|
+
@retries ||= DEFAULT_RETRIES
|
21
|
+
@legacy_postfix ||= ""
|
22
|
+
@use_jynx = true if @use_jynx.nil?
|
23
|
+
|
24
|
+
@data = YAML.load(ERB.new(File.read(file_name)).result)[env].each do |name, entry|
|
25
|
+
next unless entry.has_key?("url")
|
26
|
+
opts = {
|
27
|
+
time_window_in_seconds: entry.fetch(:time_window_in_seconds, 20),
|
28
|
+
max_errors: entry.fetch(:max_errors, 10),
|
29
|
+
grace_period: entry.fetch(:grace_period, 120)
|
30
|
+
}
|
31
|
+
|
32
|
+
ServiceJynx.register!(name.gsub(@legacy_postfix, ""), opts) if @use_jynx
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset
|
38
|
+
@report_method = nil
|
39
|
+
@timeout = nil
|
40
|
+
@user_agent = nil
|
41
|
+
@retries = nil
|
42
|
+
@use_jynx = nil
|
43
|
+
@legacy_postfix = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
## Dummy method to test reporting phase
|
47
|
+
def report_on
|
48
|
+
@report_method.call("RestfulClientConfiguration", "Initialized at: #{Time.now}.")
|
49
|
+
end
|
50
|
+
|
51
|
+
def env
|
52
|
+
@env_name || "default"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RestfulClientLogger
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def logger
|
5
|
+
@logger ||= (rails_logger || $log || default_logger)
|
6
|
+
end
|
7
|
+
|
8
|
+
def rails_logger
|
9
|
+
(defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
|
10
|
+
(defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_logger
|
14
|
+
require 'logger'
|
15
|
+
l = Logger.new(STDOUT)
|
16
|
+
l.level = Logger::INFO
|
17
|
+
l
|
18
|
+
end
|
19
|
+
|
20
|
+
def logger=(logger)
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'restful_client/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'restful_client'
|
7
|
+
spec.version = RestfulClient::VERSION
|
8
|
+
spec.authors = ['Avner Cohen']
|
9
|
+
spec.email = ['israbirding@gmail.com']
|
10
|
+
spec.description = %q{An HTTP framework for micro-services based environment, build on top of Typheous and Service Jynx}
|
11
|
+
spec.summary = %q{An HTTP framework for micro-services based environment}
|
12
|
+
spec.homepage = 'https://github.com/AvnerCohen/restful_client'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler'
|
21
|
+
spec.add_development_dependency 'rake'
|
22
|
+
|
23
|
+
spec.add_runtime_dependency 'service_jynx'
|
24
|
+
spec.add_runtime_dependency 'typhoeus'
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
default: &default
|
2
|
+
users:
|
3
|
+
url: http://1.2.3.4:7711/api/v1/
|
4
|
+
posts:
|
5
|
+
url: http://1.2.3.4:8383/api/v1/
|
6
|
+
locally:
|
7
|
+
url: http://127.0.0.1:8383/api/v1/
|
8
|
+
pompa_service:
|
9
|
+
url: http://127.0.0.1:8383/api/v1/
|
10
|
+
|
11
|
+
|
12
|
+
development: &development
|
13
|
+
<<: *default
|
14
|
+
|
15
|
+
production: &production
|
16
|
+
<<: *default
|
17
|
+
users:
|
18
|
+
url: http://1.2.3.4:7711/api/v0/
|
19
|
+
posts:
|
20
|
+
url: http://1.2.3.4:8383/api/v0/
|
21
|
+
locally:
|
22
|
+
url: http://127.0.0.1:8383/api/v0/
|
@@ -0,0 +1,394 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
def set_global(class_name)
|
6
|
+
$some_global = class_name
|
7
|
+
end
|
8
|
+
|
9
|
+
describe :RestfulClient do
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
# Start a local rack server to serve up test pages.
|
13
|
+
server_thread = Thread.new do
|
14
|
+
Rack::Handler::Thin.run MyApp::Test::Server.new, :Port => 8383
|
15
|
+
end
|
16
|
+
sleep(3) # wait a sec for the server to be booted
|
17
|
+
end
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
$some_global = nil
|
21
|
+
Typhoeus::Expectation.clear
|
22
|
+
RestfulClient.configuration.reset if RestfulClient.configuration
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Configuration" do
|
26
|
+
|
27
|
+
it "raise when file_name not sent in" do
|
28
|
+
expect { RestfulClient.configure { } }.to raise_error
|
29
|
+
end
|
30
|
+
|
31
|
+
it "correctly read configuration files" do
|
32
|
+
RestfulClient.configure do |config|
|
33
|
+
config.config_folder = "spec/config"
|
34
|
+
end
|
35
|
+
|
36
|
+
expect(RestfulClient.configuration.data).to_not eq(nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "allow accesing url configuration" do
|
40
|
+
RestfulClient.configure do |config|
|
41
|
+
config.config_folder = "spec/config"
|
42
|
+
end
|
43
|
+
|
44
|
+
expect(RestfulClient.configuration.data["posts"]["url"]).to eq("http://1.2.3.4:8383/api/v1/")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "allow accesing url configuration by environment" do
|
48
|
+
RestfulClient.configure do |config|
|
49
|
+
config.env_name = "production"
|
50
|
+
config.config_folder = "spec/config"
|
51
|
+
end
|
52
|
+
|
53
|
+
expect(RestfulClient.configuration.data["posts"]["url"]).to eq("http://1.2.3.4:8383/api/v0/")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "allow should access with legacy configuration as well" do
|
57
|
+
RestfulClient.configure do |config|
|
58
|
+
config.env_name = "production"
|
59
|
+
config.config_folder = "spec/config"
|
60
|
+
config.legacy_postfix = "_service"
|
61
|
+
end
|
62
|
+
|
63
|
+
expect(RestfulClient.callerr_config("pompa")["url"]).to_not eq(nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should register jynx with correct name in case of legacy" do
|
67
|
+
RestfulClient.configure do |config|
|
68
|
+
config.env_name = "production"
|
69
|
+
config.config_folder = "spec/config"
|
70
|
+
config.legacy_postfix = "_service"
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
expect(ServiceJynx.alive?("pompa")).to eq(true)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
it "have a logger" do
|
79
|
+
RestfulClient.configure do |config|
|
80
|
+
config.env_name = "production"
|
81
|
+
config.config_folder = "spec/config"
|
82
|
+
end
|
83
|
+
|
84
|
+
expect(RestfulClient.logger).to respond_to :info
|
85
|
+
end
|
86
|
+
|
87
|
+
it "correctly read the configuration from the registry" do
|
88
|
+
RestfulClient.configure do |config|
|
89
|
+
config.config_folder = "spec/config"
|
90
|
+
end
|
91
|
+
|
92
|
+
expect(RestfulClient.callerr_config("posts")["url"]).to eq("http://1.2.3.4:8383/api/v0/")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "have a timeout defined for the service by default" do
|
96
|
+
RestfulClient.configure do |config|
|
97
|
+
config.config_folder = "spec/config"
|
98
|
+
end
|
99
|
+
|
100
|
+
expect(RestfulClient.timeout).to eq(10)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "have a timeout defined for the service by default" do
|
104
|
+
RestfulClient.configure do |config|
|
105
|
+
config.config_folder = "spec/config"
|
106
|
+
config.timeout = 2
|
107
|
+
end
|
108
|
+
|
109
|
+
expect(RestfulClient.timeout).to eq(2)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "have a user agent defined for the service by default" do
|
113
|
+
RestfulClient.configure do |config|
|
114
|
+
config.config_folder = "spec/config"
|
115
|
+
end
|
116
|
+
|
117
|
+
expect(RestfulClient.user_agent).to eq(RestfulClientConfiguration::DEFAULT_USER_AGENT)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "have a user agent defined for the service" do
|
121
|
+
RestfulClient.configure do |config|
|
122
|
+
config.config_folder = "spec/config"
|
123
|
+
config.user_agent = "users_service"
|
124
|
+
end
|
125
|
+
|
126
|
+
expect(RestfulClient.user_agent).to eq("users_service")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "have a retries defined for the service by default" do
|
130
|
+
RestfulClient.configure do |config|
|
131
|
+
config.config_folder = "spec/config"
|
132
|
+
end
|
133
|
+
|
134
|
+
expect(RestfulClient.retries).to eq(2)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "have a retries defined for the service by default" do
|
138
|
+
RestfulClient.configure do |config|
|
139
|
+
config.config_folder = "spec/config"
|
140
|
+
config.retries = 15
|
141
|
+
end
|
142
|
+
|
143
|
+
expect(RestfulClient.retries).to eq(15)
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "toJson" do
|
149
|
+
|
150
|
+
it "automatically run to_json on an object" do
|
151
|
+
RestfulClient.configure do |config|
|
152
|
+
config.env_name = "production"
|
153
|
+
config.config_folder = "spec/config"
|
154
|
+
end
|
155
|
+
|
156
|
+
some_object = {"moshe" => "test"}
|
157
|
+
res = RestfulClient.post("locally", "/bounce", some_object) { |m| m }
|
158
|
+
|
159
|
+
expect(res).to eq(some_object)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "doesn't re-run json on data that was already a json object" do
|
163
|
+
RestfulClient.configure do |config|
|
164
|
+
config.env_name = "production"
|
165
|
+
config.config_folder = "spec/config"
|
166
|
+
end
|
167
|
+
|
168
|
+
some_object = {"moshe" => "test"}.to_json
|
169
|
+
res = RestfulClient.post("locally", "/bounce", some_object) { |m| m }
|
170
|
+
|
171
|
+
expect(res).to eq(JSON.parse(some_object))
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "Restful Client Calls" do
|
177
|
+
|
178
|
+
it "allow sending in a reporting method (such as graylog/ airbrake instrumentiation)" do
|
179
|
+
RestfulClient.configure do |config|
|
180
|
+
config.env_name = "production"
|
181
|
+
config.config_folder = "spec/config"
|
182
|
+
config.report_method = proc do |*args|
|
183
|
+
klass, message = *args
|
184
|
+
set_global(klass)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
RestfulClient.configuration.report_on
|
188
|
+
|
189
|
+
expect($some_global).to eq("RestfulClientConfiguration")
|
190
|
+
end
|
191
|
+
|
192
|
+
it "report on a server error (end-2-end ish test)" do
|
193
|
+
RestfulClient.configure do |config|
|
194
|
+
config.env_name = "production"
|
195
|
+
config.config_folder = "spec/config"
|
196
|
+
config.timeout = 1
|
197
|
+
config.report_method = proc do |*args|
|
198
|
+
klass, message = *args
|
199
|
+
set_global(klass)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
RestfulClient.get("posts", "/a/a/a/a") { nil }
|
203
|
+
|
204
|
+
expect($some_global).to eq("RestError")
|
205
|
+
end
|
206
|
+
|
207
|
+
it "do not report on a client side error (http_code < 500)" do
|
208
|
+
RestfulClient.configure do |config|
|
209
|
+
config.env_name = "production"
|
210
|
+
config.config_folder = "spec/config"
|
211
|
+
config.report_method = proc do |*args|
|
212
|
+
klass, message = *args
|
213
|
+
set_global(klass)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
RestfulClient.get("locally", "/client_error") { nil }
|
217
|
+
|
218
|
+
expect($some_global).to be_nil
|
219
|
+
end
|
220
|
+
|
221
|
+
it "do not report on a missing resource (http_code == 404)" do
|
222
|
+
RestfulClient.configure do |config|
|
223
|
+
config.env_name = "production"
|
224
|
+
config.config_folder = "spec/config"
|
225
|
+
config.report_method = proc do |*args|
|
226
|
+
klass, message = *args
|
227
|
+
set_global(klass)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
RestfulClient.get("locally", "/non_existing") { nil }
|
232
|
+
|
233
|
+
expect($some_global).to be_nil
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
describe "Retry call count" do
|
239
|
+
|
240
|
+
it "repeat the call if retry is set " do
|
241
|
+
|
242
|
+
RestfulClient.configure do |config|
|
243
|
+
config.config_folder = "spec/config"
|
244
|
+
config.retries = 3
|
245
|
+
config.timeout = 2
|
246
|
+
config.report_method = proc do |*args|
|
247
|
+
klass, message = *args
|
248
|
+
set_global(klass)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# flow is a little wierdish
|
253
|
+
##Run something that hard codedly, in the test_server, increments the counter
|
254
|
+
RestfulClient.get("locally", "a/a/a/b") { |m| nil }
|
255
|
+
# Now checksomething that returns "200", put also the global server counter
|
256
|
+
# And asset the number of retries.
|
257
|
+
res = RestfulClient.get("locally", "a") { "good" }
|
258
|
+
|
259
|
+
expect(res["counter"]).to eq(4)
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
describe "Default Value if no block is given" do
|
265
|
+
|
266
|
+
it "should return nil when no value is given" do
|
267
|
+
RestfulClient.configure do |config|
|
268
|
+
config.config_folder = "spec/config"
|
269
|
+
config.timeout = 1
|
270
|
+
end
|
271
|
+
res = RestfulClient.get("posts", "/a")
|
272
|
+
expect(res).to eq(nil)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should allow skiping jynx completly" do
|
276
|
+
RestfulClient.configure do |config|
|
277
|
+
config.config_folder = "spec/config"
|
278
|
+
config.use_jynx = false
|
279
|
+
config.timeout = 1
|
280
|
+
end
|
281
|
+
res = RestfulClient.get("posts", "/a")
|
282
|
+
expect(res).to eq(nil)
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "Uri Joining" do
|
288
|
+
|
289
|
+
it "correct join two paths leading slash defined as [WithSlash : NoSlash]" do
|
290
|
+
path = RestfulClientUri.uri_join("http://load-balancer-int01:9999/api/v1/", "proposals/sent/count/123")
|
291
|
+
expect(path).to eq("http://load-balancer-int01:9999/api/v1/proposals/sent/count/123")
|
292
|
+
end
|
293
|
+
|
294
|
+
it "correct join two paths leading slash defined as [WithSlash : WithSlash]" do
|
295
|
+
path = RestfulClientUri.uri_join("http://load-balancer-int01:9999/api/v1/", "/proposals/sent/count/123")
|
296
|
+
expect(path).to eq("http://load-balancer-int01:9999/api/v1/proposals/sent/count/123")
|
297
|
+
end
|
298
|
+
|
299
|
+
it "correct join two paths leading slash defined as [NoSlash : WithSlash]" do
|
300
|
+
path = RestfulClientUri.uri_join("http://load-balancer-int01:9999/api/v1", "/proposals/sent/count/123")
|
301
|
+
expect(path).to eq("http://load-balancer-int01:9999/api/v1/proposals/sent/count/123")
|
302
|
+
end
|
303
|
+
|
304
|
+
it "correct join two paths leading slash defined as [NoSlash : NowSlash]" do
|
305
|
+
path = RestfulClientUri.uri_join("http://load-balancer-int01:9999/api/v1", "proposals/sent/count/123")
|
306
|
+
expect(path).to eq("http://load-balancer-int01:9999/api/v1/proposals/sent/count/123")
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
describe "Fake response" do
|
312
|
+
|
313
|
+
it "should return fake response" do
|
314
|
+
RestfulClient.configure { |config| config.config_folder = "spec/config" }
|
315
|
+
|
316
|
+
RestfulClient.fake('locally', 'fake/me') do
|
317
|
+
Typhoeus::Response.new(
|
318
|
+
code: 200,
|
319
|
+
headers: {'Content-Type' => 'application/json'},
|
320
|
+
body: { 'fake' => true }.to_json)
|
321
|
+
end
|
322
|
+
|
323
|
+
res = RestfulClient.get('locally', 'fake/me') { nil }
|
324
|
+
expect(res).to eq({ 'fake' => true })
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
describe "custom headers" do
|
330
|
+
it "should allow sending custom headers to a single post request" do
|
331
|
+
RestfulClient.configure do |config|
|
332
|
+
config.env_name = "production"
|
333
|
+
config.config_folder = "spec/config"
|
334
|
+
end
|
335
|
+
|
336
|
+
custom_headers = {"moshe" => "OH MY GOD ~!!"}
|
337
|
+
res = RestfulClient.post("locally", "/bounce_headers/moshe", {}, {"headers" => custom_headers})
|
338
|
+
expect(res).to eq(custom_headers)
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should allow sending custom headers to a single get request" do
|
343
|
+
RestfulClient.configure do |config|
|
344
|
+
config.env_name = "production"
|
345
|
+
config.config_folder = "spec/config"
|
346
|
+
end
|
347
|
+
|
348
|
+
custom_headers = {"moshe" => "OH MY GOD ~!!"}
|
349
|
+
res = RestfulClient.get("locally", "/bounce_headers/moshe", nil, {"headers" => custom_headers})
|
350
|
+
expect(res).to eq(custom_headers)
|
351
|
+
|
352
|
+
end
|
353
|
+
|
354
|
+
it "should allow sending custom headers to a single delete request" do
|
355
|
+
RestfulClient.configure do |config|
|
356
|
+
config.env_name = "production"
|
357
|
+
config.config_folder = "spec/config"
|
358
|
+
end
|
359
|
+
|
360
|
+
custom_headers = {"moshe" => "OH MY GOD ~!!"}
|
361
|
+
res = RestfulClient.delete("locally", "/bounce_headers/moshe", {}, {"headers" => custom_headers})
|
362
|
+
expect(res).to eq(custom_headers)
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should allow sending custom headers to a single put request" do
|
367
|
+
RestfulClient.configure do |config|
|
368
|
+
config.env_name = "production"
|
369
|
+
config.config_folder = "spec/config"
|
370
|
+
end
|
371
|
+
|
372
|
+
custom_headers = {"moshe" => "OH MY GOD ~!!"}
|
373
|
+
res = RestfulClient.put("locally", "/bounce_headers/moshe", {}, {"headers" => custom_headers})
|
374
|
+
expect(res).to eq(custom_headers)
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
describe :PlainURL do
|
382
|
+
it "should be possible to get url from yml for custom calls" do
|
383
|
+
RestfulClient.configure do |config|
|
384
|
+
config.env_name = "production"
|
385
|
+
config.config_folder = "spec/config"
|
386
|
+
end
|
387
|
+
|
388
|
+
expect(RestfulClient.srv_url('posts')).to eq("http://1.2.3.4:8383/api/v0/")
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
end
|
394
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module MyApp
|
6
|
+
module Test
|
7
|
+
class Server
|
8
|
+
@@counter = 0
|
9
|
+
def call(env)
|
10
|
+
path = Rack::Utils.unescape(env['PATH_INFO'])
|
11
|
+
### Hacky just to allow sending a global counter
|
12
|
+
if path == "/api/v0/a/a/a/b"
|
13
|
+
@@counter = @@counter+1
|
14
|
+
sleep 3
|
15
|
+
[ 500, {"Content-Type" => "application/json"}, {counter: @@counter}.to_json ]
|
16
|
+
elsif path == "/api/v0/bounce"
|
17
|
+
[ 200, {'Content-Type' => 'text/plain'}, env['rack.input'].gets ]
|
18
|
+
elsif path == "/api/v0/non_existing"
|
19
|
+
[ 404, {'Content-Type' => 'text/plain'}, {}.to_json ]
|
20
|
+
elsif path == "/api/v0/client_error"
|
21
|
+
[ 400, {'Content-Type' => 'text/plain'}, {}.to_json ]
|
22
|
+
elsif path =~ /\/api\/v0\/bounce_headers/
|
23
|
+
## this is what rack request is doing to custom headers
|
24
|
+
base_name = path.split("/").last
|
25
|
+
header_name = "HTTP_#{base_name.upcase}"
|
26
|
+
[ 200, {'Content-Type' => 'text/plain'}, {"#{base_name}" => env[header_name]}.to_json ]
|
27
|
+
else
|
28
|
+
[ 200, {'Content-Type' => 'text/plain'}, {counter: @@counter}.to_json ]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: restful_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Avner Cohen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: service_jynx
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: typhoeus
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: An HTTP framework for micro-services based environment, build on top
|
70
|
+
of Typheous and Service Jynx
|
71
|
+
email:
|
72
|
+
- israbirding@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".ruby-gemset"
|
80
|
+
- ".ruby-version"
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- lib/restful_client.rb
|
85
|
+
- lib/restful_client/version.rb
|
86
|
+
- lib/restful_client_configuration.rb
|
87
|
+
- lib/restful_client_logger.rb
|
88
|
+
- lib/restful_client_uri.rb
|
89
|
+
- restful_client.gemspec
|
90
|
+
- spec/config/restful_services.yml
|
91
|
+
- spec/restful_client_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
- spec/support/test_server.rb
|
94
|
+
homepage: https://github.com/AvnerCohen/restful_client
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.4.5
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: An HTTP framework for micro-services based environment
|
118
|
+
test_files:
|
119
|
+
- spec/config/restful_services.yml
|
120
|
+
- spec/restful_client_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/support/test_server.rb
|