rest-core 0.0.1

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