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