rest-core 1.0.3 → 2.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.
- data/.travis.yml +6 -7
- data/CHANGES.md +137 -0
- data/Gemfile +1 -1
- data/README.md +183 -191
- data/TODO.md +5 -8
- data/example/multi.rb +31 -24
- data/example/simple.rb +28 -0
- data/example/use-cases.rb +194 -0
- data/lib/rest-core.rb +26 -19
- data/lib/rest-core/builder.rb +2 -2
- data/lib/rest-core/client.rb +40 -27
- data/lib/rest-core/client/universal.rb +16 -13
- data/lib/rest-core/client_oauth1.rb +5 -5
- data/lib/rest-core/engine/auto.rb +25 -0
- data/lib/rest-core/{app → engine}/dry.rb +1 -2
- data/lib/rest-core/engine/em-http-request.rb +39 -0
- data/lib/rest-core/engine/future/future.rb +106 -0
- data/lib/rest-core/engine/future/future_fiber.rb +39 -0
- data/lib/rest-core/engine/future/future_thread.rb +29 -0
- data/lib/rest-core/engine/rest-client.rb +56 -0
- data/lib/rest-core/middleware.rb +27 -5
- data/lib/rest-core/middleware/auth_basic.rb +5 -5
- data/lib/rest-core/middleware/bypass.rb +2 -2
- data/lib/rest-core/middleware/cache.rb +67 -54
- data/lib/rest-core/middleware/common_logger.rb +5 -8
- data/lib/rest-core/middleware/default_headers.rb +2 -2
- data/lib/rest-core/middleware/default_payload.rb +26 -2
- data/lib/rest-core/middleware/default_query.rb +4 -2
- data/lib/rest-core/middleware/default_site.rb +8 -6
- data/lib/rest-core/middleware/error_detector.rb +9 -16
- data/lib/rest-core/middleware/error_handler.rb +25 -11
- data/lib/rest-core/middleware/follow_redirect.rb +11 -14
- data/lib/rest-core/middleware/json_request.rb +19 -0
- data/lib/rest-core/middleware/json_response.rb +28 -0
- data/lib/rest-core/middleware/oauth1_header.rb +2 -7
- data/lib/rest-core/middleware/oauth2_header.rb +4 -7
- data/lib/rest-core/middleware/oauth2_query.rb +2 -2
- data/lib/rest-core/middleware/timeout.rb +21 -65
- data/lib/rest-core/middleware/timeout/{eventmachine_timer.rb → timer_em.rb} +3 -1
- data/lib/rest-core/middleware/timeout/timer_thread.rb +36 -0
- data/lib/rest-core/patch/multi_json.rb +8 -0
- data/lib/rest-core/test.rb +3 -12
- data/lib/rest-core/util/json.rb +65 -0
- data/lib/rest-core/util/parse_query.rb +2 -2
- data/lib/rest-core/version.rb +1 -1
- data/lib/rest-core/wrapper.rb +16 -16
- data/rest-core.gemspec +28 -27
- data/test/test_auth_basic.rb +14 -10
- data/test/test_builder.rb +7 -7
- data/test/test_cache.rb +126 -37
- data/test/test_client.rb +3 -1
- data/test/test_client_oauth1.rb +2 -3
- data/test/test_default_query.rb +17 -23
- data/test/test_em_http_request.rb +146 -0
- data/test/test_error_detector.rb +0 -1
- data/test/test_error_handler.rb +44 -0
- data/test/test_follow_redirect.rb +17 -19
- data/test/test_json_request.rb +28 -0
- data/test/test_json_response.rb +51 -0
- data/test/test_oauth1_header.rb +4 -4
- data/test/test_payload.rb +20 -12
- data/test/test_simple.rb +14 -0
- data/test/test_timeout.rb +11 -19
- data/test/test_universal.rb +5 -5
- data/test/test_wrapper.rb +19 -13
- metadata +28 -29
- data/doc/ToC.md +0 -7
- data/doc/dependency.md +0 -4
- data/doc/design.md +0 -4
- data/example/auto.rb +0 -51
- data/example/coolio.rb +0 -21
- data/example/eventmachine.rb +0 -30
- data/example/rest-client.rb +0 -16
- data/lib/rest-core/app/abstract/async_fiber.rb +0 -13
- data/lib/rest-core/app/auto.rb +0 -23
- data/lib/rest-core/app/coolio-async.rb +0 -32
- data/lib/rest-core/app/coolio-fiber.rb +0 -30
- data/lib/rest-core/app/coolio.rb +0 -9
- data/lib/rest-core/app/em-http-request-async.rb +0 -37
- data/lib/rest-core/app/em-http-request-fiber.rb +0 -45
- data/lib/rest-core/app/em-http-request.rb +0 -9
- data/lib/rest-core/app/rest-client.rb +0 -41
- data/lib/rest-core/middleware/json_decode.rb +0 -93
- data/lib/rest-core/middleware/timeout/coolio_timer.rb +0 -10
- data/pending/test_multi.rb +0 -123
- data/pending/test_test_util.rb +0 -86
- data/test/test_json_decode.rb +0 -24
@@ -15,86 +15,99 @@ class RestCore::Cache
|
|
15
15
|
@app, @cache, @expires_in = app, cache, expires_in
|
16
16
|
end
|
17
17
|
|
18
|
-
def call env
|
19
|
-
e = if env['cache.update']
|
20
|
-
|
18
|
+
def call env, &k
|
19
|
+
e = if env['cache.update']
|
20
|
+
cache_clear(env)
|
21
21
|
else
|
22
22
|
env
|
23
23
|
end
|
24
24
|
|
25
25
|
if cached = cache_get(e)
|
26
|
-
|
27
|
-
wrapped.call(cached)
|
26
|
+
e[TIMER].cancel if e[TIMER]
|
27
|
+
wrapped.call(cached, &k)
|
28
28
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
app.call(e){ |res|
|
30
|
+
wrapped.call(res){ |res_wrapped|
|
31
|
+
k.call(if (res_wrapped[FAIL] || []).empty?
|
32
|
+
cache_for(res).merge(res_wrapped)
|
33
|
+
else
|
34
|
+
res_wrapped
|
35
|
+
end)}}
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def process env, response
|
40
|
-
if env[ASYNC]
|
41
|
-
wrapped.call(response.merge(ASYNC => lambda{ |response_wrapped|
|
42
|
-
env[ASYNC].call(process_wrapped(env, response, response_wrapped))
|
43
|
-
}))
|
44
|
-
else
|
45
|
-
process_wrapped(env, response, wrapped.call(response))
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def process_wrapped env, response, response_wrapped
|
50
|
-
if (response_wrapped[FAIL] || []).empty?
|
51
|
-
cache_for(env, response).merge(response_wrapped)
|
52
|
-
else
|
53
|
-
response_wrapped
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
protected
|
58
39
|
def cache_key env
|
59
|
-
Digest::MD5.hexdigest(env['cache.key'] ||
|
40
|
+
"rest-core:cache:#{Digest::MD5.hexdigest(env['cache.key'] ||
|
41
|
+
cache_key_raw(env))}"
|
60
42
|
end
|
61
43
|
|
62
44
|
def cache_get env
|
63
45
|
return unless cache(env)
|
46
|
+
return unless cache_for?(env)
|
47
|
+
|
64
48
|
start_time = Time.now
|
65
|
-
return unless
|
49
|
+
return unless data = cache(env)[cache_key(env)]
|
66
50
|
log(env, Event::CacheHit.new(Time.now - start_time, request_uri(env))).
|
67
|
-
merge(
|
51
|
+
merge(data_extract(data))
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def cache_key_raw env
|
56
|
+
"#{env[REQUEST_METHOD]}:#{request_uri(env)}:#{header_cache_key(env)}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def cache_clear env
|
60
|
+
return env unless cache(env)
|
61
|
+
return env unless cache_for?(env)
|
62
|
+
|
63
|
+
cache_store(env, :[]=, nil)
|
68
64
|
end
|
69
65
|
|
70
|
-
def cache_for
|
71
|
-
return
|
72
|
-
|
73
|
-
return response if env[REQUEST_METHOD] != :get unless env['cache.post']
|
66
|
+
def cache_for res
|
67
|
+
return res unless cache(res)
|
68
|
+
return res unless cache_for?(res)
|
74
69
|
|
75
|
-
|
70
|
+
if expires_in(res).kind_of?(Fixnum) &&
|
71
|
+
cache(res).respond_to?(:store) &&
|
72
|
+
cache(res).method(:store).arity == -3
|
76
73
|
|
77
|
-
|
78
|
-
|
79
|
-
cache(env).method(:store).arity == -3
|
80
|
-
cache(env).store(cache_key(env), value,
|
81
|
-
:expires_in => expires_in(env))
|
82
|
-
response
|
74
|
+
cache_store(res, :store, data_construct(res),
|
75
|
+
:expires_in => expires_in(res))
|
83
76
|
else
|
84
|
-
|
77
|
+
cache_store(res, :[]= , data_construct(res))
|
85
78
|
end
|
86
79
|
end
|
87
80
|
|
88
|
-
def
|
89
|
-
return env unless cache(env)
|
90
|
-
|
81
|
+
def cache_store res, msg, value, *args
|
91
82
|
start_time = Time.now
|
92
|
-
cache(
|
93
|
-
|
94
|
-
|
95
|
-
|
83
|
+
cache(res).send(msg, cache_key(res), value, *args)
|
84
|
+
|
85
|
+
if value
|
86
|
+
res
|
96
87
|
else
|
97
|
-
|
88
|
+
log(res,
|
89
|
+
Event::CacheCleared.new(Time.now - start_time, request_uri(res)))
|
98
90
|
end
|
99
91
|
end
|
92
|
+
|
93
|
+
def data_construct res
|
94
|
+
"#{ res[RESPONSE_STATUS]}\n" \
|
95
|
+
"#{(res[RESPONSE_HEADERS]||{}).map{|k,v|"#{k}: #{v}"}.join("\n")}\n\n" \
|
96
|
+
"#{ res[RESPONSE_BODY]}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def data_extract data
|
100
|
+
_, status, headers, body = data.match(/\A(\d+)\n(.*)\n\n(.*)\Z/m).to_a
|
101
|
+
{RESPONSE_BODY => body,
|
102
|
+
RESPONSE_HEADERS => Hash[(headers||'').scan(/([^:]+): ([^\n]+)\n?/)],
|
103
|
+
RESPONSE_STATUS => status.to_i}
|
104
|
+
end
|
105
|
+
|
106
|
+
def cache_for? env
|
107
|
+
[:get, :head, :otpions].include?(env[REQUEST_METHOD])
|
108
|
+
end
|
109
|
+
|
110
|
+
def header_cache_key env
|
111
|
+
(env[REQUEST_HEADERS]||{}).sort.map{|(k,v)|"#{k}=#{v}"}.join('&')
|
112
|
+
end
|
100
113
|
end
|
@@ -9,13 +9,9 @@ class RestCore::CommonLogger
|
|
9
9
|
def call env
|
10
10
|
start_time = Time.now
|
11
11
|
flushed = flush(env)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}))
|
16
|
-
else
|
17
|
-
process(app.call(flushed), start_time)
|
18
|
-
end
|
12
|
+
app.call(flushed){ |response|
|
13
|
+
yield(process(response, start_time))
|
14
|
+
}
|
19
15
|
rescue
|
20
16
|
process(flushed, start_time)
|
21
17
|
raise
|
@@ -33,6 +29,7 @@ class RestCore::CommonLogger
|
|
33
29
|
end
|
34
30
|
|
35
31
|
def log_request start_time, response
|
36
|
-
Event::Requested.new(Time.now - start_time,
|
32
|
+
Event::Requested.new(Time.now - start_time,
|
33
|
+
"#{response[RC::REQUEST_METHOD].to_s.upcase} #{request_uri(response)}")
|
37
34
|
end
|
38
35
|
end
|
@@ -4,8 +4,8 @@ require 'rest-core/middleware'
|
|
4
4
|
class RestCore::DefaultHeaders
|
5
5
|
def self.members; [:headers]; end
|
6
6
|
include RestCore::Middleware
|
7
|
-
def call env
|
7
|
+
def call env, &k
|
8
8
|
app.call(env.merge(REQUEST_HEADERS =>
|
9
|
-
@headers.merge(headers(env)).merge(env[REQUEST_HEADERS] || {})))
|
9
|
+
@headers.merge(headers(env)).merge(env[REQUEST_HEADERS] || {})), &k)
|
10
10
|
end
|
11
11
|
end
|
@@ -4,8 +4,32 @@ require 'rest-core/middleware'
|
|
4
4
|
class RestCore::DefaultPayload
|
5
5
|
def self.members; [:payload]; end
|
6
6
|
include RestCore::Middleware
|
7
|
-
|
7
|
+
|
8
|
+
def initialize *args
|
9
|
+
super
|
10
|
+
@payload ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def call env, &k
|
14
|
+
defaults = merge(@payload, payload(env))
|
15
|
+
|
8
16
|
app.call(env.merge(REQUEST_PAYLOAD =>
|
9
|
-
|
17
|
+
merge(defaults, env[REQUEST_PAYLOAD] || {})), &k)
|
18
|
+
end
|
19
|
+
|
20
|
+
# this method is intended to merge payloads if they are non-empty hashes,
|
21
|
+
# but prefer the right most one if they are not hashes.
|
22
|
+
def merge lhs, rhs
|
23
|
+
if rhs.respond_to?(:empty?) && rhs.empty?
|
24
|
+
lhs
|
25
|
+
elsif lhs.respond_to?(:merge)
|
26
|
+
if rhs.respond_to?(:merge)
|
27
|
+
string_keys(lhs).merge(string_keys(rhs))
|
28
|
+
else
|
29
|
+
rhs
|
30
|
+
end
|
31
|
+
else
|
32
|
+
rhs
|
33
|
+
end
|
10
34
|
end
|
11
35
|
end
|
@@ -10,8 +10,10 @@ class RestCore::DefaultQuery
|
|
10
10
|
@query ||= {}
|
11
11
|
end
|
12
12
|
|
13
|
-
def call env
|
13
|
+
def call env, &k
|
14
|
+
defaults = string_keys(@query).merge(string_keys(query(env)))
|
15
|
+
|
14
16
|
app.call(env.merge(REQUEST_QUERY =>
|
15
|
-
|
17
|
+
defaults.merge(env[REQUEST_QUERY] || {})), &k)
|
16
18
|
end
|
17
19
|
end
|
@@ -5,11 +5,13 @@ class RestCore::DefaultSite
|
|
5
5
|
def self.members; [:site]; end
|
6
6
|
include RestCore::Middleware
|
7
7
|
|
8
|
-
def call env
|
9
|
-
if env[REQUEST_PATH].to_s.start_with?('http')
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def call env, &k
|
9
|
+
path = if env[REQUEST_PATH].to_s.start_with?('http')
|
10
|
+
env[REQUEST_PATH]
|
11
|
+
else
|
12
|
+
"#{site(env)}#{env[REQUEST_PATH]}"
|
13
|
+
end
|
14
|
+
|
15
|
+
app.call(env.merge(REQUEST_PATH => path), &k)
|
14
16
|
end
|
15
17
|
end
|
@@ -6,21 +6,14 @@ class RestCore::ErrorDetector
|
|
6
6
|
include RestCore::Middleware
|
7
7
|
|
8
8
|
def call env
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def process env, response
|
19
|
-
detector = error_detector(env)
|
20
|
-
if error = (detector && detector.call(response))
|
21
|
-
fail(response, error)
|
22
|
-
else
|
23
|
-
response
|
24
|
-
end
|
9
|
+
app.call(env){ |response|
|
10
|
+
detector = error_detector(env)
|
11
|
+
yield(
|
12
|
+
if error = (detector && detector.call(response))
|
13
|
+
fail(response, error)
|
14
|
+
else
|
15
|
+
response
|
16
|
+
end)
|
17
|
+
}
|
25
18
|
end
|
26
19
|
end
|
@@ -6,20 +6,34 @@ class RestCore::ErrorHandler
|
|
6
6
|
include RestCore::Middleware
|
7
7
|
|
8
8
|
def call env
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
app.call(env){ |res|
|
10
|
+
yield(if (res[FAIL] || []).empty? # no errors at all
|
11
|
+
res
|
12
|
+
else
|
13
|
+
# if there's an exception, hand it over
|
14
|
+
if err = res[FAIL].find{ |e| e.kind_of?(Exception) }
|
15
|
+
process(res, err)
|
16
|
+
|
17
|
+
elsif h = error_handler(res)
|
18
|
+
# if the user provides an exception, hand it over
|
19
|
+
if (err = h.call(res)).kind_of?(Exception)
|
20
|
+
process(res, err)
|
21
|
+
|
22
|
+
else # otherwise we report all of them
|
23
|
+
res.merge(FAIL => [res[FAIL], err].flatten.compact)
|
24
|
+
|
25
|
+
end
|
26
|
+
else # no exceptions at all, then do nothing
|
27
|
+
res
|
28
|
+
end
|
29
|
+
end)}
|
16
30
|
end
|
17
31
|
|
18
|
-
def
|
19
|
-
if
|
20
|
-
|
32
|
+
def process res, err
|
33
|
+
if res[ASYNC]
|
34
|
+
res.merge(RESPONSE_BODY => err)
|
21
35
|
else
|
22
|
-
|
36
|
+
raise err
|
23
37
|
end
|
24
38
|
end
|
25
39
|
end
|
@@ -5,26 +5,23 @@ class RestCore::FollowRedirect
|
|
5
5
|
def self.members; [:max_redirects]; end
|
6
6
|
include RestCore::Middleware
|
7
7
|
|
8
|
-
def call env
|
8
|
+
def call env, &k
|
9
9
|
e = env.merge('follow_redirect.max_redirects' =>
|
10
10
|
env['follow_redirect.max_redirects'] ||
|
11
11
|
max_redirects(env))
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
app.call(e.merge(ASYNC => lambda{ |response|
|
16
|
-
e[ASYNC].call(process(response))
|
17
|
-
}))
|
13
|
+
if e[DRY]
|
14
|
+
app.call(e, &k)
|
18
15
|
else
|
19
|
-
|
16
|
+
app.call(e){ |res| process(res, k) }
|
20
17
|
end
|
21
18
|
end
|
22
19
|
|
23
|
-
def process res
|
24
|
-
return res if res['follow_redirect.max_redirects'] <= 0
|
25
|
-
return res if ![301,302,303,307].include?(res[RESPONSE_STATUS])
|
26
|
-
return res if [301,302 ,307].include?(res[RESPONSE_STATUS]) &&
|
27
|
-
|
20
|
+
def process res, k
|
21
|
+
return k.call(res) if res['follow_redirect.max_redirects'] <= 0
|
22
|
+
return k.call(res) if ![301,302,303,307].include?(res[RESPONSE_STATUS])
|
23
|
+
return k.call(res) if [301,302 ,307].include?(res[RESPONSE_STATUS]) &&
|
24
|
+
![:get, :head ].include?(res[REQUEST_METHOD])
|
28
25
|
|
29
26
|
location = [res[RESPONSE_HEADERS]['LOCATION']].flatten.first
|
30
27
|
meth = if res[RESPONSE_STATUS] == 303
|
@@ -35,8 +32,8 @@ class RestCore::FollowRedirect
|
|
35
32
|
|
36
33
|
call(res.merge(REQUEST_PATH => location,
|
37
34
|
REQUEST_METHOD => meth ,
|
38
|
-
REQUEST_PAYLOAD =>
|
35
|
+
REQUEST_PAYLOAD => {} ,
|
39
36
|
'follow_redirect.max_redirects' =>
|
40
|
-
res['follow_redirect.max_redirects'] - 1))
|
37
|
+
res['follow_redirect.max_redirects'] - 1), &k)
|
41
38
|
end
|
42
39
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
require 'rest-core/util/json'
|
4
|
+
|
5
|
+
class RestCore::JsonRequest
|
6
|
+
def self.members; [:json_request]; end
|
7
|
+
include RestCore::Middleware
|
8
|
+
|
9
|
+
JSON_REQUEST_HEADER = {'Content-Type' => 'application/json'}.freeze
|
10
|
+
|
11
|
+
def call env, &k
|
12
|
+
return app.call(env, &k) unless json_request(env)
|
13
|
+
return app.call(env, &k) unless env[REQUEST_PAYLOAD]
|
14
|
+
|
15
|
+
app.call(env.merge(
|
16
|
+
REQUEST_HEADERS => JSON_REQUEST_HEADER.merge(env[REQUEST_HEADERS]||{}),
|
17
|
+
REQUEST_PAYLOAD => Json.encode(env[REQUEST_PAYLOAD]) ), &k)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
require 'rest-core/util/json'
|
4
|
+
|
5
|
+
class RestCore::JsonResponse
|
6
|
+
def self.members; [:json_response]; end
|
7
|
+
include RestCore::Middleware
|
8
|
+
|
9
|
+
JSON_RESPONSE_HEADER = {'Accept' => 'application/json'}.freeze
|
10
|
+
|
11
|
+
def call env, &k
|
12
|
+
return app.call(env, &k) if env[DRY]
|
13
|
+
return app.call(env, &k) unless json_response(env)
|
14
|
+
|
15
|
+
app.call(env.merge(REQUEST_HEADERS =>
|
16
|
+
JSON_RESPONSE_HEADER.merge(env[REQUEST_HEADERS]||{}))){ |response|
|
17
|
+
yield(process(response))
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def process response
|
22
|
+
response.merge(RESPONSE_BODY =>
|
23
|
+
Json.decode("[#{response[RESPONSE_BODY]}]").first)
|
24
|
+
# [this].first is not needed for yajl-ruby
|
25
|
+
rescue Json.const_get(:ParseError) => error
|
26
|
+
fail(response, error)
|
27
|
+
end
|
28
|
+
end
|
@@ -15,7 +15,7 @@ class RestCore::Oauth1Header
|
|
15
15
|
:oauth_token, :oauth_token_secret, :data]
|
16
16
|
end
|
17
17
|
include RestCore::Middleware
|
18
|
-
def call env
|
18
|
+
def call env, &k
|
19
19
|
start_time = Time.now
|
20
20
|
headers = {'Authorization' => oauth_header(env)}.
|
21
21
|
merge(env[REQUEST_HEADERS] || {})
|
@@ -23,12 +23,7 @@ class RestCore::Oauth1Header
|
|
23
23
|
event = Event::WithHeader.new(Time.now - start_time,
|
24
24
|
"Authorization: #{headers['Authorization']}")
|
25
25
|
|
26
|
-
app.call(log(
|
27
|
-
end
|
28
|
-
|
29
|
-
def cache_key env
|
30
|
-
env.merge('cache.key' =>
|
31
|
-
"#{request_uri(env)}&#{oauth_token(env)}&#{oauth_token_secret(env)}")
|
26
|
+
app.call(log(env.merge(REQUEST_HEADERS => headers), event), &k)
|
32
27
|
end
|
33
28
|
|
34
29
|
def oauth_header env
|