rest-core 3.6.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitmodules +3 -0
- data/.travis.yml +0 -1
- data/CHANGES.md +28 -0
- data/Gemfile +0 -2
- data/README.md +14 -95
- data/Rakefile +16 -3
- data/example/simple.rb +1 -0
- data/example/use-cases.rb +15 -3
- data/lib/rest-core.rb +38 -75
- data/lib/rest-core/client/universal.rb +3 -1
- data/lib/rest-core/client_oauth1.rb +64 -59
- data/lib/rest-core/event.rb +9 -11
- data/lib/rest-core/middleware/auth_basic.rb +21 -21
- data/lib/rest-core/middleware/bypass.rb +8 -8
- data/lib/rest-core/middleware/cache.rb +94 -90
- data/lib/rest-core/middleware/clash_response.rb +15 -14
- data/lib/rest-core/middleware/common_logger.rb +27 -26
- data/lib/rest-core/middleware/default_headers.rb +8 -8
- data/lib/rest-core/middleware/default_payload.rb +8 -8
- data/lib/rest-core/middleware/default_query.rb +8 -8
- data/lib/rest-core/middleware/default_site.rb +12 -12
- data/lib/rest-core/middleware/defaults.rb +38 -38
- data/lib/rest-core/middleware/error_detector.rb +10 -10
- data/lib/rest-core/middleware/error_detector_http.rb +6 -4
- data/lib/rest-core/middleware/error_handler.rb +14 -14
- data/lib/rest-core/middleware/follow_redirect.rb +28 -27
- data/lib/rest-core/middleware/json_request.rb +13 -11
- data/lib/rest-core/middleware/json_response.rb +29 -28
- data/lib/rest-core/middleware/oauth1_header.rb +84 -83
- data/lib/rest-core/middleware/oauth2_header.rb +27 -25
- data/lib/rest-core/middleware/oauth2_query.rb +15 -15
- data/lib/rest-core/middleware/query_response.rb +14 -14
- data/lib/rest-core/middleware/retry.rb +25 -23
- data/lib/rest-core/middleware/smash_response.rb +15 -14
- data/lib/rest-core/middleware/timeout.rb +18 -19
- data/lib/rest-core/test.rb +1 -18
- data/lib/rest-core/util/clash.rb +38 -37
- data/lib/rest-core/util/config.rb +40 -39
- data/lib/rest-core/util/dalli_extension.rb +11 -10
- data/lib/rest-core/util/hmac.rb +9 -8
- data/lib/rest-core/util/json.rb +55 -54
- data/lib/rest-core/util/parse_link.rb +13 -12
- data/lib/rest-core/util/parse_query.rb +24 -22
- data/lib/rest-core/version.rb +1 -1
- data/rest-core.gemspec +121 -158
- data/test/test_cache.rb +2 -0
- data/test/test_default_payload.rb +1 -1
- data/test/test_error_handler.rb +5 -4
- data/test/test_timeout.rb +9 -8
- data/test/test_universal.rb +8 -0
- metadata +9 -73
- data/lib/rest-core/builder.rb +0 -162
- data/lib/rest-core/client.rb +0 -277
- data/lib/rest-core/client/simple.rb +0 -2
- data/lib/rest-core/engine.rb +0 -36
- data/lib/rest-core/engine/dry.rb +0 -9
- data/lib/rest-core/engine/http-client.rb +0 -41
- data/lib/rest-core/error.rb +0 -5
- data/lib/rest-core/event_source.rb +0 -137
- data/lib/rest-core/middleware.rb +0 -151
- data/lib/rest-core/promise.rb +0 -249
- data/lib/rest-core/thread_pool.rb +0 -131
- data/lib/rest-core/timer.rb +0 -58
- data/lib/rest-core/util/payload.rb +0 -173
- data/test/test_builder.rb +0 -40
- data/test/test_client.rb +0 -177
- data/test/test_event_source.rb +0 -159
- data/test/test_future.rb +0 -16
- data/test/test_httpclient.rb +0 -118
- data/test/test_payload.rb +0 -204
- data/test/test_promise.rb +0 -146
- data/test/test_simple.rb +0 -38
- data/test/test_thread_pool.rb +0 -10
@@ -1,39 +1,40 @@
|
|
1
1
|
|
2
|
-
require 'rest-core/middleware'
|
3
2
|
require 'rest-core/util/json'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module RestCore
|
5
|
+
class JsonResponse
|
6
|
+
def self.members; [:json_response]; end
|
7
|
+
include Middleware
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
class ParseError < Json.const_get(:ParseError)
|
10
|
+
attr_reader :cause, :body
|
11
|
+
def initialize cause, body
|
12
|
+
msg = cause.message.force_encoding('utf-8')
|
13
|
+
super("#{msg}\nOriginal text: #{body}")
|
14
|
+
@cause, @body = cause, body
|
15
|
+
end
|
15
16
|
end
|
16
|
-
end
|
17
17
|
|
18
|
-
|
18
|
+
JSON_RESPONSE_HEADER = {'Accept' => 'application/json'}.freeze
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
def call env, &k
|
21
|
+
return app.call(env, &k) if env[DRY]
|
22
|
+
return app.call(env, &k) unless json_response(env)
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
app.call(env.merge(REQUEST_HEADERS =>
|
25
|
+
JSON_RESPONSE_HEADER.merge(env[REQUEST_HEADERS]||{}))){ |response|
|
26
|
+
yield(process(response))
|
27
|
+
}
|
28
|
+
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
def process response
|
31
|
+
# StackExchange returns the problematic BOM! in UTF-8, so we need to
|
32
|
+
# strip it or it would break JSON parsers (i.e. yajl-ruby and json)
|
33
|
+
body = response[RESPONSE_BODY].to_s.sub(/\A\xEF\xBB\xBF/, '')
|
34
|
+
response.merge(RESPONSE_BODY => Json.decode("[#{body}]").first)
|
35
|
+
# [this].first is not needed for yajl-ruby
|
36
|
+
rescue Json.const_get(:ParseError) => error
|
37
|
+
fail(response, ParseError.new(error, body))
|
38
|
+
end
|
38
39
|
end
|
39
40
|
end
|
@@ -1,100 +1,101 @@
|
|
1
1
|
|
2
|
-
require '
|
2
|
+
require 'openssl'
|
3
|
+
require 'rest-core/event'
|
3
4
|
require 'rest-core/util/hmac'
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
module RestCore
|
7
|
+
# http://tools.ietf.org/html/rfc5849
|
8
|
+
class Oauth1Header
|
9
|
+
def self.members
|
10
|
+
[:request_token_path, :access_token_path, :authorize_path,
|
11
|
+
:consumer_key, :consumer_secret,
|
12
|
+
:oauth_callback, :oauth_verifier,
|
13
|
+
:oauth_token, :oauth_token_secret, :data]
|
14
|
+
end
|
15
|
+
include Middleware
|
7
16
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
:consumer_key, :consumer_secret,
|
13
|
-
:oauth_callback, :oauth_verifier,
|
14
|
-
:oauth_token, :oauth_token_secret, :data]
|
15
|
-
end
|
16
|
-
include RestCore::Middleware
|
17
|
-
def call env, &k
|
18
|
-
start_time = Time.now
|
19
|
-
headers = {'Authorization' => oauth_header(env)}.
|
20
|
-
merge(env[REQUEST_HEADERS])
|
17
|
+
def call env, &k
|
18
|
+
start_time = Time.now
|
19
|
+
headers = {'Authorization' => oauth_header(env)}.
|
20
|
+
merge(env[REQUEST_HEADERS])
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
event = Event::WithHeader.new(Time.now - start_time,
|
23
|
+
"Authorization: #{headers['Authorization']}")
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
app.call(log(env.merge(REQUEST_HEADERS => headers), event), &k)
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
28
|
+
def oauth_header env
|
29
|
+
oauth = attach_signature(env,
|
30
|
+
'oauth_consumer_key' => consumer_key(env),
|
31
|
+
'oauth_signature_method' => 'HMAC-SHA1',
|
32
|
+
'oauth_timestamp' => Time.now.to_i.to_s,
|
33
|
+
'oauth_nonce' => nonce,
|
34
|
+
'oauth_version' => '1.0',
|
35
|
+
'oauth_callback' => oauth_callback(env),
|
36
|
+
'oauth_verifier' => oauth_verifier(env),
|
37
|
+
'oauth_token' => oauth_token(env)).
|
38
|
+
map{ |(k, v)| "#{k}=\"#{escape(v)}\"" }.join(', ')
|
39
|
+
|
40
|
+
"OAuth #{oauth}"
|
41
|
+
end
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
def attach_signature env, oauth_params
|
44
|
+
params = reject_blank(oauth_params)
|
45
|
+
params.merge('oauth_signature' => signature(env, params))
|
46
|
+
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
def signature env, params
|
49
|
+
[Hmac.sha1("#{consumer_secret(env)}&#{oauth_token_secret(env)}",
|
50
|
+
base_string(env, params))].pack('m0')
|
51
|
+
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
def base_string env, oauth_params
|
54
|
+
method = env[REQUEST_METHOD].to_s.upcase
|
55
|
+
base_uri = env[REQUEST_PATH]
|
56
|
+
payload = payload_params(env)
|
57
|
+
query = reject_blank(env[REQUEST_QUERY])
|
58
|
+
params = reject_blank(oauth_params.merge(query.merge(payload))).
|
59
|
+
to_a.sort.map{ |(k, v)|
|
60
|
+
"#{escape(k.to_s)}=#{escape(v.to_s)}"}.join('&')
|
61
61
|
|
62
|
-
|
63
|
-
|
62
|
+
"#{method}&#{escape(base_uri)}&#{escape(params)}"
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
def nonce
|
66
|
+
[OpenSSL::Random.random_bytes(32)].pack('m0').tr("+/=", '')
|
67
|
+
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
69
|
+
# according to OAuth 1.0a spec, only:
|
70
|
+
# Content-Type: application/x-www-form-urlencoded
|
71
|
+
# should take payload as a part of the base_string
|
72
|
+
def payload_params env
|
73
|
+
# if we already specified Content-Type and which is not
|
74
|
+
# application/x-www-form-urlencoded, then we should not
|
75
|
+
# take payload as a part of the base_string
|
76
|
+
if env[REQUEST_HEADERS].kind_of?(Hash) &&
|
77
|
+
env[REQUEST_HEADERS]['Content-Type'] &&
|
78
|
+
env[REQUEST_HEADERS]['Content-Type'] !=
|
79
|
+
'application/x-www-form-urlencoded'
|
80
|
+
{}
|
81
|
+
|
82
|
+
# if it contains any binary data,
|
83
|
+
# then it shouldn't be application/x-www-form-urlencoded either
|
84
|
+
# the Content-Type header would be handled in our HTTP client
|
85
|
+
elsif contain_binary?(env[REQUEST_PAYLOAD])
|
86
|
+
{}
|
87
|
+
|
88
|
+
# so the Content-Type header must be application/x-www-form-urlencoded
|
89
|
+
else
|
90
|
+
reject_blank(env[REQUEST_PAYLOAD])
|
91
|
+
end
|
91
92
|
end
|
92
|
-
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
94
|
+
def reject_blank params
|
95
|
+
params.reject{ |k, v| v.nil? || v == false ||
|
96
|
+
(v.respond_to?(:strip) &&
|
97
|
+
v.respond_to?(:empty) &&
|
98
|
+
v.strip.empty? == true) }
|
99
|
+
end
|
99
100
|
end
|
100
101
|
end
|
@@ -1,33 +1,35 @@
|
|
1
1
|
|
2
|
-
require 'rest-core/
|
2
|
+
require 'rest-core/event'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module RestCore
|
5
|
+
# http://tools.ietf.org/html/rfc6749
|
6
|
+
class Oauth2Header
|
7
|
+
def self.members; [:access_token_type, :access_token]; end
|
8
|
+
include Middleware
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
def call env, &k
|
11
|
+
start_time = Time.now
|
12
|
+
headers = build_headers(env)
|
13
|
+
auth = headers['Authorization']
|
14
|
+
event = Event::WithHeader.new(Time.now - start_time,
|
15
|
+
"Authorization: #{auth}") if auth
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
app.call(log(env.merge(REQUEST_HEADERS => headers), event), &k)
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
def build_headers env
|
21
|
+
auth = case token = access_token(env)
|
22
|
+
when String
|
23
|
+
token
|
24
|
+
when Hash
|
25
|
+
token.map{ |(k, v)| "#{k}=\"#{v}\"" }.join(', ')
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
if auth
|
29
|
+
{'Authorization' => "#{access_token_type(env)} #{auth}"}
|
30
|
+
else
|
31
|
+
{}
|
32
|
+
end.merge(env[REQUEST_HEADERS])
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
module RestCore
|
3
|
+
# http://tools.ietf.org/html/rfc6749
|
4
|
+
class Oauth2Query
|
5
|
+
def self.members; [:access_token]; end
|
6
|
+
include Middleware
|
3
7
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
+
def call env, &k
|
9
|
+
local = if access_token(env)
|
10
|
+
env.merge(REQUEST_QUERY =>
|
11
|
+
{'access_token' => access_token(env)}.
|
12
|
+
merge(env[REQUEST_QUERY]))
|
13
|
+
else
|
14
|
+
env
|
15
|
+
end
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
env.merge(REQUEST_QUERY =>
|
12
|
-
{'access_token' => access_token(env)}.
|
13
|
-
merge(env[REQUEST_QUERY]))
|
14
|
-
else
|
15
|
-
env
|
16
|
-
end
|
17
|
-
|
18
|
-
app.call(local, &k)
|
17
|
+
app.call(local, &k)
|
18
|
+
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,23 +1,23 @@
|
|
1
1
|
|
2
|
-
require 'rest-core/middleware'
|
3
2
|
require 'rest-core/util/parse_query'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module RestCore
|
5
|
+
class QueryResponse
|
6
|
+
def self.members; [:query_response]; end
|
7
|
+
include Middleware
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
QUERY_RESPONSE_HEADER =
|
10
|
+
{'Accept' => 'application/x-www-form-urlencoded'}.freeze
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def call env, &k
|
13
|
+
return app.call(env, &k) if env[DRY]
|
14
|
+
return app.call(env, &k) unless query_response(env)
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
headers = QUERY_RESPONSE_HEADER.merge(env[REQUEST_HEADERS]||{})
|
17
|
+
app.call(env.merge(REQUEST_HEADERS => headers)) do |response|
|
18
|
+
body = ParseQuery.parse_query(response[RESPONSE_BODY])
|
19
|
+
yield(response.merge(RESPONSE_BODY => body))
|
20
|
+
end
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
23
|
-
|
@@ -1,33 +1,35 @@
|
|
1
1
|
|
2
|
-
require 'rest-core/
|
2
|
+
require 'rest-core/event'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module RestCore
|
5
|
+
class Retry
|
6
|
+
def self.members; [:max_retries, :retry_exceptions]; end
|
7
|
+
include Middleware
|
7
8
|
|
8
|
-
|
9
|
+
DefaultRetryExceptions = [IOError, SystemCallError]
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def call env, &k
|
12
|
+
if env[DRY]
|
13
|
+
app.call(env, &k)
|
14
|
+
else
|
15
|
+
app.call(env){ |res| process(res, k) }
|
16
|
+
end
|
15
17
|
end
|
16
|
-
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def process res, k
|
20
|
+
times = max_retries(res)
|
21
|
+
return k.call(res) if times <= 0
|
22
|
+
errors = retry_exceptions(res) || DefaultRetryExceptions
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
env,
|
29
|
-
|
30
|
-
|
24
|
+
if idx = res[FAIL].index{ |f| errors.find{ |e| f.kind_of?(e) } }
|
25
|
+
err = res[FAIL].delete_at(idx)
|
26
|
+
error_callback(res, err)
|
27
|
+
env = res.merge('max_retries' => times - 1)
|
28
|
+
event = Event::Retrying.new(nil, "(#{times}) for: #{err.inspect}")
|
29
|
+
give_promise(call(log(env, event), &k))
|
30
|
+
else
|
31
|
+
k.call(res)
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|