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 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --drb
3
+ --format documentation
4
+ --require spec_helper
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
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'thin', '~> 1.6'
4
+
5
+ gem 'pry-byebug', '~> 3.2.0', {}.merge(ENV['RM_INFO'] ? {require: false} : {})
6
+ gem 'rspec', '~> 3.1'
7
+ gem 'rspec-its', '~> 1.1'
8
+
9
+ gemspec
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
+ [![Build status](https://travis-ci.org/AvnerCohen/restful-client.png)](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,3 @@
1
+ module RestfulClient
2
+ VERSION = "0.1.0"
3
+ end
@@ -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,11 @@
1
+ require 'uri'
2
+
3
+ module RestfulClientUri
4
+ module_function
5
+
6
+ ## This is totally wierd, but File.join just does what you need while URI.join as much too strict.
7
+ def uri_join(url, path)
8
+ File.join(url, path).to_s
9
+ end
10
+
11
+ 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
+
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'rspec'
5
+
6
+ Dir["./spec/support/**/*.rb"].each {|f| load(f)}
7
+ load("#{File.dirname(__FILE__)}/../lib/restful_client.rb")
@@ -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