rest-core 0.0.1
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/.gitignore +6 -0
- data/.gitmodules +3 -0
- data/.travis.yml +9 -0
- data/CONTRIBUTORS +11 -0
- data/Gemfile +8 -0
- data/LICENSE +201 -0
- data/NOTE.md +48 -0
- data/README +83 -0
- data/README.md +83 -0
- data/Rakefile +26 -0
- data/TODO.md +17 -0
- data/example/facebook.rb +145 -0
- data/example/github.rb +21 -0
- data/lib/rest-core.rb +48 -0
- data/lib/rest-core/app/ask.rb +11 -0
- data/lib/rest-core/app/rest-client.rb +24 -0
- data/lib/rest-core/builder.rb +24 -0
- data/lib/rest-core/client.rb +278 -0
- data/lib/rest-core/client/github.rb +19 -0
- data/lib/rest-core/client/linkedin.rb +57 -0
- data/lib/rest-core/client/rest-graph.rb +262 -0
- data/lib/rest-core/client/twitter.rb +59 -0
- data/lib/rest-core/client_oauth1.rb +25 -0
- data/lib/rest-core/event.rb +17 -0
- data/lib/rest-core/middleware.rb +53 -0
- data/lib/rest-core/middleware/cache.rb +80 -0
- data/lib/rest-core/middleware/common_logger.rb +27 -0
- data/lib/rest-core/middleware/default_headers.rb +11 -0
- data/lib/rest-core/middleware/default_query.rb +11 -0
- data/lib/rest-core/middleware/default_site.rb +15 -0
- data/lib/rest-core/middleware/defaults.rb +44 -0
- data/lib/rest-core/middleware/error_detector.rb +16 -0
- data/lib/rest-core/middleware/error_detector_http.rb +11 -0
- data/lib/rest-core/middleware/error_handler.rb +19 -0
- data/lib/rest-core/middleware/json_decode.rb +83 -0
- data/lib/rest-core/middleware/oauth1_header.rb +81 -0
- data/lib/rest-core/middleware/oauth2_query.rb +19 -0
- data/lib/rest-core/middleware/timeout.rb +13 -0
- data/lib/rest-core/util/hmac.rb +22 -0
- data/lib/rest-core/version.rb +4 -0
- data/lib/rest-core/wrapper.rb +55 -0
- data/lib/rest-graph/config_util.rb +43 -0
- data/rest-core.gemspec +162 -0
- data/task/.gitignore +1 -0
- data/task/gemgem.rb +184 -0
- data/test/common.rb +29 -0
- data/test/config/rest-graph.yaml +7 -0
- data/test/pending/test_load_config.rb +42 -0
- data/test/pending/test_multi.rb +123 -0
- data/test/pending/test_test_util.rb +86 -0
- data/test/test_api.rb +98 -0
- data/test/test_cache.rb +62 -0
- data/test/test_default.rb +27 -0
- data/test/test_error.rb +66 -0
- data/test/test_handler.rb +87 -0
- data/test/test_misc.rb +75 -0
- data/test/test_oauth.rb +42 -0
- data/test/test_oauth1_header.rb +46 -0
- data/test/test_old.rb +116 -0
- data/test/test_page.rb +110 -0
- data/test/test_parse.rb +131 -0
- data/test/test_rest-graph.rb +10 -0
- data/test/test_serialize.rb +44 -0
- data/test/test_timeout.rb +25 -0
- metadata +267 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/event'
|
3
|
+
require 'rest-core/middleware'
|
4
|
+
require 'rest-core/wrapper'
|
5
|
+
|
6
|
+
require 'digest/md5'
|
7
|
+
|
8
|
+
class RestCore::Cache
|
9
|
+
def self.members; [:cache, :expires_in]; end
|
10
|
+
include RestCore::Middleware
|
11
|
+
include RestCore::Wrapper
|
12
|
+
|
13
|
+
def initialize app, cache, expires_in, &block
|
14
|
+
super(&block)
|
15
|
+
@app, @cache, @expires_in = app, cache, expires_in
|
16
|
+
end
|
17
|
+
|
18
|
+
def call env
|
19
|
+
e = if env['cache.update'] && env[REQUEST_METHOD] == :get
|
20
|
+
cache_assign(env, nil)
|
21
|
+
else
|
22
|
+
env
|
23
|
+
end
|
24
|
+
|
25
|
+
if cached = cache_get(e)
|
26
|
+
wrapped.call(cached)
|
27
|
+
else
|
28
|
+
response = app.call(e)
|
29
|
+
response_wrapped = wrapped.call(response)
|
30
|
+
if (response_wrapped[FAIL] || []).empty?
|
31
|
+
cache_for(e, response).merge(response_wrapped)
|
32
|
+
else
|
33
|
+
response_wrapped
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def cache_key env
|
40
|
+
Digest::MD5.hexdigest(env['cache.key'] || request_uri(env))
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_get env
|
44
|
+
return unless cache(env)
|
45
|
+
start_time = Time.now
|
46
|
+
return unless value = cache(env)[cache_key(env)]
|
47
|
+
log(env, Event::CacheHit.new(Time.now - start_time, request_uri(env))).
|
48
|
+
merge(RESPONSE_BODY => value)
|
49
|
+
end
|
50
|
+
|
51
|
+
def cache_for env, response
|
52
|
+
return response unless cache(env)
|
53
|
+
# fake post (env['cache.post'] => true) is considered get and need cache
|
54
|
+
return response if env[REQUEST_METHOD] != :get unless env['cache.post']
|
55
|
+
|
56
|
+
value = response[RESPONSE_BODY]
|
57
|
+
|
58
|
+
if expires_in(env).kind_of?(Fixnum) &&
|
59
|
+
cache(env).method(:store).arity == -3
|
60
|
+
cache(env).store(cache_key(env), value,
|
61
|
+
:expires_in => expires_in(env))
|
62
|
+
response
|
63
|
+
else
|
64
|
+
cache_assign(response, value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def cache_assign env, value
|
69
|
+
return env unless cache(env)
|
70
|
+
|
71
|
+
start_time = Time.now
|
72
|
+
cache(env)[cache_key(env)] = value
|
73
|
+
if value.nil?
|
74
|
+
log(env,
|
75
|
+
Event::CacheCleared.new(Time.now - start_time, request_uri(env)))
|
76
|
+
else
|
77
|
+
env
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/event'
|
3
|
+
require 'rest-core/middleware'
|
4
|
+
|
5
|
+
class RestCore::CommonLogger
|
6
|
+
def self.members; [:log_method]; end
|
7
|
+
include RestCore::Middleware
|
8
|
+
|
9
|
+
def call env
|
10
|
+
start_time = Time.now
|
11
|
+
response = app.call(flushed = flush(env))
|
12
|
+
flush(log(response, log_request(start_time, response)))
|
13
|
+
rescue
|
14
|
+
flush(log(flushed, log_request(start_time, flushed)))
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
|
18
|
+
def flush env
|
19
|
+
return env if !log_method(env) || env[ASK]
|
20
|
+
(env[LOG] || []).each{ |obj| log_method(env).call("RestCore: #{obj}") }
|
21
|
+
env.merge(LOG => [])
|
22
|
+
end
|
23
|
+
|
24
|
+
def log_request start_time, response
|
25
|
+
Event::Requested.new(Time.now - start_time, request_uri(response))
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::DefaultHeaders
|
5
|
+
def self.members; [:headers]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
def call env
|
8
|
+
app.call(env.merge(REQUEST_HEADERS =>
|
9
|
+
@headers.merge(headers(env)).merge(env[REQUEST_HEADERS] || {})))
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::DefaultSite
|
5
|
+
def self.members; [:site]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
def call env
|
9
|
+
if env[REQUEST_PATH].to_s.start_with?('http')
|
10
|
+
app.call(env)
|
11
|
+
else
|
12
|
+
app.call(env.merge(REQUEST_PATH => "#{site(env)}#{env[REQUEST_PATH]}"))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::Defaults
|
5
|
+
def self.members; [:defaults]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
# the use of singleton_class is making serialization hard!
|
9
|
+
# def initialize app, defaults
|
10
|
+
# super
|
11
|
+
# singleton_class.module_eval do
|
12
|
+
# defaults.each{ |(key, value)|
|
13
|
+
# define_method(key) do |env|
|
14
|
+
# if value.respond_to?(:call)
|
15
|
+
# value.call
|
16
|
+
# else
|
17
|
+
# value
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# }
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
|
24
|
+
def method_missing msg, *args, &block
|
25
|
+
env = args.first
|
26
|
+
if env.kind_of?(Hash) && (d = defaults(env)) && d.key?(msg)
|
27
|
+
if (value = defaults(env)[msg]).respond_to?(:call)
|
28
|
+
value.call
|
29
|
+
else
|
30
|
+
value
|
31
|
+
end
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to? msg
|
38
|
+
if (d = defaults({})) && d.key?(msg)
|
39
|
+
true
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::ErrorDetector
|
5
|
+
def self.members; [:error_detector]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
def call env
|
9
|
+
response = app.call(env)
|
10
|
+
if error = error_detector(env).call(response)
|
11
|
+
fail(response, error)
|
12
|
+
else
|
13
|
+
response
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware/error_detector'
|
3
|
+
|
4
|
+
class RestCore::ErrorDetectorHttp < RestCore::ErrorDetector
|
5
|
+
def self.members; [:error_detector]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
def initialize app
|
9
|
+
super(app, lambda{ |env| (env[RESPONSE_STATUS] || 200) / 100 != 2 })
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::ErrorHandler
|
5
|
+
def self.members; [:error_handler]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
def call env
|
9
|
+
handle(app.call(handle(env)))
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle env
|
13
|
+
if error_handler(env) && !(env[FAIL] || []).empty?
|
14
|
+
error_handler(env).call(env)
|
15
|
+
else
|
16
|
+
env
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::JsonDecode
|
5
|
+
def self.members; [:json_decode]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
def call env
|
9
|
+
response = app.call(env)
|
10
|
+
if json_decode(env)
|
11
|
+
response.merge(RESPONSE_BODY =>
|
12
|
+
self.class.json_decode("[#{response[RESPONSE_BODY]}]").first)
|
13
|
+
# [this].first is not needed for yajl-ruby
|
14
|
+
else
|
15
|
+
response
|
16
|
+
end
|
17
|
+
rescue self.class.const_get(:ParseError) => error
|
18
|
+
fail(response, error)
|
19
|
+
end
|
20
|
+
|
21
|
+
module YajlRuby
|
22
|
+
def self.extended mod
|
23
|
+
mod.const_set(:ParseError, Yajl::ParseError)
|
24
|
+
end
|
25
|
+
def json_encode hash
|
26
|
+
Yajl::Encoder.encode(hash)
|
27
|
+
end
|
28
|
+
def json_decode json
|
29
|
+
Yajl::Parser.parse(json)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Json
|
34
|
+
def self.extended mod
|
35
|
+
mod.const_set(:ParseError, JSON::ParserError)
|
36
|
+
end
|
37
|
+
def json_encode hash
|
38
|
+
JSON.dump(hash)
|
39
|
+
end
|
40
|
+
def json_decode json
|
41
|
+
JSON.parse(json)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Gsub
|
46
|
+
class ParseError < RuntimeError; end
|
47
|
+
def self.extended mod
|
48
|
+
mod.const_set(:ParseError, Gsub::ParseError)
|
49
|
+
end
|
50
|
+
# only works for flat hash
|
51
|
+
def json_encode hash
|
52
|
+
middle = hash.inject([]){ |r, (k, v)|
|
53
|
+
r << "\"#{k}\":\"#{v.gsub('"','\\"')}\""
|
54
|
+
}.join(',')
|
55
|
+
"{#{middle}}"
|
56
|
+
end
|
57
|
+
def json_decode json
|
58
|
+
raise NotImplementedError.new(
|
59
|
+
'You need to install either yajl-ruby, json, or json_pure gem')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.select_json! mod, picked=false
|
64
|
+
if Object.const_defined?(:Yajl)
|
65
|
+
mod.send(:extend, YajlRuby)
|
66
|
+
elsif Object.const_defined?(:JSON)
|
67
|
+
mod.send(:extend, Json)
|
68
|
+
elsif picked
|
69
|
+
mod.send(:extend, Gsub)
|
70
|
+
else
|
71
|
+
# pick a json gem if available
|
72
|
+
%w[yajl json].each{ |json|
|
73
|
+
begin
|
74
|
+
require json
|
75
|
+
break
|
76
|
+
rescue LoadError
|
77
|
+
end
|
78
|
+
}
|
79
|
+
select_json!(mod, true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
select_json!(self)
|
83
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
require 'rest-core/util/hmac'
|
4
|
+
|
5
|
+
require 'uri'
|
6
|
+
require 'openssl'
|
7
|
+
|
8
|
+
class RestCore::Oauth1Header
|
9
|
+
def self.members
|
10
|
+
[:request_token_path, :access_token_path, :authorize_path,
|
11
|
+
:consumer_key, :consumer_secret,
|
12
|
+
:callback, :verifier, :oauth_token, :oauth_token_secret]
|
13
|
+
end
|
14
|
+
include RestCore::Middleware
|
15
|
+
def call env
|
16
|
+
start_time = Time.now
|
17
|
+
headers = {'Authorization' => oauth_header(env)}.
|
18
|
+
merge(env[REQUEST_HEADERS] || {})
|
19
|
+
|
20
|
+
event = Event::WithHeader.new(Time.now - start_time,
|
21
|
+
"Authorization: #{headers['Authorization']}")
|
22
|
+
|
23
|
+
app.call(log(cache_key(env.merge(REQUEST_HEADERS => headers)), event))
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_key env
|
27
|
+
env.merge('cache.key' =>
|
28
|
+
"#{request_uri(env)}&#{oauth_token(env)}&#{oauth_token_secret(env)}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def oauth_header env
|
32
|
+
header = attach_signature(env,
|
33
|
+
'oauth_consumer_key' => consumer_key(env),
|
34
|
+
'oauth_signature_method' => 'HMAC-SHA1',
|
35
|
+
'oauth_timestamp' => Time.now.to_i.to_s,
|
36
|
+
'oauth_nonce' => nonce,
|
37
|
+
'oauth_version' => '1.0',
|
38
|
+
'oauth_callback' => callback(env),
|
39
|
+
'oauth_verifier' => verifier(env),
|
40
|
+
'oauth_token' => oauth_token(env))
|
41
|
+
|
42
|
+
"OAuth #{header.map{ |(k, v)| "#{k}=\"#{v}\"" }.join(', ')}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def attach_signature env, oauth_params
|
46
|
+
params = reject_blank(oauth_params)
|
47
|
+
params.merge('oauth_signature' => encode(signature(env, params)))
|
48
|
+
end
|
49
|
+
|
50
|
+
def signature env, params
|
51
|
+
[Hmac.sha1("#{consumer_secret(env)}&#{oauth_token_secret(env)}",
|
52
|
+
base_string(env, params))].pack('m').tr("\n", '')
|
53
|
+
end
|
54
|
+
|
55
|
+
def base_string env, oauth_params
|
56
|
+
method = env[REQUEST_METHOD].to_s.upcase
|
57
|
+
base_uri = env[REQUEST_PATH]
|
58
|
+
query = reject_blank(env[REQUEST_QUERY] || {})
|
59
|
+
payload = reject_blank(env[REQUEST_PAYLOAD] || {})
|
60
|
+
params = reject_blank(oauth_params.merge(query.merge(payload))).
|
61
|
+
to_a.sort.map{ |(k, v)|
|
62
|
+
"#{encode(k.to_s)}=#{encode(v.to_s)}"}.join('&')
|
63
|
+
|
64
|
+
"#{method}&#{encode(base_uri)}&#{encode(params)}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def nonce
|
68
|
+
[OpenSSL::Random.random_bytes(32)].pack('m').tr("+/=\n", '')
|
69
|
+
end
|
70
|
+
|
71
|
+
def reject_blank params
|
72
|
+
params.reject{ |k, v| v.nil? || v == false ||
|
73
|
+
(v.respond_to?(:strip) &&
|
74
|
+
v.respond_to?(:empty) &&
|
75
|
+
v.strip.empty? == true) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def encode string
|
79
|
+
URI.encode(string, /[^a-zA-Z0-9\-\.\_\~]/)
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
class RestCore::Oauth2Query
|
5
|
+
def self.members; [:oauth_token_name, :oauth_token]; end
|
6
|
+
include RestCore::Middleware
|
7
|
+
|
8
|
+
def call env
|
9
|
+
local = if oauth_token(env)
|
10
|
+
env.merge(REQUEST_QUERY =>
|
11
|
+
{oauth_token_name(env) => oauth_token(env)}.
|
12
|
+
merge(env[REQUEST_QUERY] || {}))
|
13
|
+
else
|
14
|
+
env
|
15
|
+
end
|
16
|
+
|
17
|
+
app.call(local)
|
18
|
+
end
|
19
|
+
end
|