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