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.
Files changed (65) hide show
  1. data/.gitignore +6 -0
  2. data/.gitmodules +3 -0
  3. data/.travis.yml +9 -0
  4. data/CONTRIBUTORS +11 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE +201 -0
  7. data/NOTE.md +48 -0
  8. data/README +83 -0
  9. data/README.md +83 -0
  10. data/Rakefile +26 -0
  11. data/TODO.md +17 -0
  12. data/example/facebook.rb +145 -0
  13. data/example/github.rb +21 -0
  14. data/lib/rest-core.rb +48 -0
  15. data/lib/rest-core/app/ask.rb +11 -0
  16. data/lib/rest-core/app/rest-client.rb +24 -0
  17. data/lib/rest-core/builder.rb +24 -0
  18. data/lib/rest-core/client.rb +278 -0
  19. data/lib/rest-core/client/github.rb +19 -0
  20. data/lib/rest-core/client/linkedin.rb +57 -0
  21. data/lib/rest-core/client/rest-graph.rb +262 -0
  22. data/lib/rest-core/client/twitter.rb +59 -0
  23. data/lib/rest-core/client_oauth1.rb +25 -0
  24. data/lib/rest-core/event.rb +17 -0
  25. data/lib/rest-core/middleware.rb +53 -0
  26. data/lib/rest-core/middleware/cache.rb +80 -0
  27. data/lib/rest-core/middleware/common_logger.rb +27 -0
  28. data/lib/rest-core/middleware/default_headers.rb +11 -0
  29. data/lib/rest-core/middleware/default_query.rb +11 -0
  30. data/lib/rest-core/middleware/default_site.rb +15 -0
  31. data/lib/rest-core/middleware/defaults.rb +44 -0
  32. data/lib/rest-core/middleware/error_detector.rb +16 -0
  33. data/lib/rest-core/middleware/error_detector_http.rb +11 -0
  34. data/lib/rest-core/middleware/error_handler.rb +19 -0
  35. data/lib/rest-core/middleware/json_decode.rb +83 -0
  36. data/lib/rest-core/middleware/oauth1_header.rb +81 -0
  37. data/lib/rest-core/middleware/oauth2_query.rb +19 -0
  38. data/lib/rest-core/middleware/timeout.rb +13 -0
  39. data/lib/rest-core/util/hmac.rb +22 -0
  40. data/lib/rest-core/version.rb +4 -0
  41. data/lib/rest-core/wrapper.rb +55 -0
  42. data/lib/rest-graph/config_util.rb +43 -0
  43. data/rest-core.gemspec +162 -0
  44. data/task/.gitignore +1 -0
  45. data/task/gemgem.rb +184 -0
  46. data/test/common.rb +29 -0
  47. data/test/config/rest-graph.yaml +7 -0
  48. data/test/pending/test_load_config.rb +42 -0
  49. data/test/pending/test_multi.rb +123 -0
  50. data/test/pending/test_test_util.rb +86 -0
  51. data/test/test_api.rb +98 -0
  52. data/test/test_cache.rb +62 -0
  53. data/test/test_default.rb +27 -0
  54. data/test/test_error.rb +66 -0
  55. data/test/test_handler.rb +87 -0
  56. data/test/test_misc.rb +75 -0
  57. data/test/test_oauth.rb +42 -0
  58. data/test/test_oauth1_header.rb +46 -0
  59. data/test/test_old.rb +116 -0
  60. data/test/test_page.rb +110 -0
  61. data/test/test_parse.rb +131 -0
  62. data/test/test_rest-graph.rb +10 -0
  63. data/test/test_serialize.rb +44 -0
  64. data/test/test_timeout.rb +25 -0
  65. 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,11 @@
1
+
2
+ require 'rest-core/middleware'
3
+
4
+ class RestCore::DefaultQuery
5
+ def self.members; [:query]; end
6
+ include RestCore::Middleware
7
+ def call env
8
+ app.call(env.merge(REQUEST_QUERY =>
9
+ @query.merge(query(env)).merge(env[REQUEST_QUERY] || {})))
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
@@ -0,0 +1,13 @@
1
+
2
+ require 'rest-core/middleware'
3
+
4
+ require 'timeout'
5
+
6
+ class RestCore::Timeout
7
+ def self.members; [:timeout]; end
8
+ include RestCore::Middleware
9
+
10
+ def call env
11
+ ::Timeout.timeout(timeout(env)){ app.call(env) }
12
+ end
13
+ end