restful_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ 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