rest-core 3.6.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|