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
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "#{dir = File.dirname(__FILE__)}/task/gemgem"
|
4
|
+
Gemgem.dir = dir
|
5
|
+
|
6
|
+
($LOAD_PATH << File.expand_path("#{Gemgem.dir}/lib" )).uniq!
|
7
|
+
|
8
|
+
desc 'Generate gemspec'
|
9
|
+
task 'gem:spec' do
|
10
|
+
Gemgem.spec = Gemgem.create do |s|
|
11
|
+
require 'rest-core/version'
|
12
|
+
s.name = 'rest-core'
|
13
|
+
s.version = RestCore::VERSION
|
14
|
+
s.homepage = 'https://github.com/cardinalblue/rest-core'
|
15
|
+
# s.executables = [s.name]
|
16
|
+
|
17
|
+
%w[].each{ |g| s.add_runtime_dependency(g) }
|
18
|
+
%w[rest-client rack yajl-ruby json json_pure ruby-hmac
|
19
|
+
webmock bacon rr rake].each{ |g| s.add_development_dependency(g) }
|
20
|
+
|
21
|
+
s.authors = ['Cardinal Blue', 'Lin Jen-Shin (godfat)']
|
22
|
+
s.email = ['dev (XD) cardinalblue.com']
|
23
|
+
end
|
24
|
+
|
25
|
+
Gemgem.write
|
26
|
+
end
|
data/TODO.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
## high
|
4
|
+
|
5
|
+
* middleware revisit (how to initialize?)
|
6
|
+
* what does false and nil mean in env?
|
7
|
+
|
8
|
+
## medium
|
9
|
+
|
10
|
+
* namespace issue
|
11
|
+
* what about async request? yield callback? coroutine?
|
12
|
+
* dependency?
|
13
|
+
|
14
|
+
## low
|
15
|
+
|
16
|
+
* config loader
|
17
|
+
* test utility
|
data/example/facebook.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
|
2
|
+
# simple client
|
3
|
+
|
4
|
+
require 'rest-core'
|
5
|
+
|
6
|
+
RestCore::Builder.client('Facebook', :data, :app_id, :secret, :old_site) do
|
7
|
+
s = self.class # this is only for ruby 1.8!
|
8
|
+
use s::Timeout , 10
|
9
|
+
|
10
|
+
use s::DefaultSite , 'https://graph.facebook.com/'
|
11
|
+
use s::DefaultHeaders, {'Accept' => 'application/json',
|
12
|
+
'Accept-Language' => 'en-us'}
|
13
|
+
use s::Oauth2Query , 'access_token', nil
|
14
|
+
|
15
|
+
use s::CommonLogger , method(:puts)
|
16
|
+
use s::Cache , {}, nil
|
17
|
+
use s::ErrorHandler , lambda{ |env| raise ::Facebook::Error.call(env) }
|
18
|
+
use s::ErrorDetector , lambda{ |env| env[s::RESPONSE_BODY]['error'] ||
|
19
|
+
env[s::RESPONSE_BODY]['error_code'] }
|
20
|
+
use s::JsonDecode , true
|
21
|
+
|
22
|
+
use s::Defaults , :data => lambda{{}},
|
23
|
+
:old_site => 'https://api.facebook.com/'
|
24
|
+
|
25
|
+
run s::RestClient
|
26
|
+
end
|
27
|
+
|
28
|
+
class Facebook::Error < RuntimeError
|
29
|
+
include RestCore
|
30
|
+
|
31
|
+
attr_reader :error, :url
|
32
|
+
def initialize error, url=''
|
33
|
+
@error, @url = error, url
|
34
|
+
super("#{error.inspect} from #{url}")
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.call env
|
38
|
+
error, url = env[RESPONSE_BODY], Middleware.request_uri(env)
|
39
|
+
new(error, url)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Facebook::Client
|
44
|
+
include RestCore
|
45
|
+
|
46
|
+
def oauth_token
|
47
|
+
data['access_token'] || data['oauth_token'] if data.kind_of?(Hash)
|
48
|
+
end
|
49
|
+
def oauth_token= token
|
50
|
+
data['access_token'] = token if data.kind_of?(Hash)
|
51
|
+
end
|
52
|
+
alias_method :access_token , :oauth_token
|
53
|
+
alias_method :access_token=, :oauth_token=
|
54
|
+
|
55
|
+
def secret_oauth_token ; "#{app_id}|#{secret}" ; end
|
56
|
+
alias_method :secret_access_token, :secret_oauth_token
|
57
|
+
|
58
|
+
def accept ; headers['Accept'] ; end
|
59
|
+
def accept= val; headers['Accept'] = val; end
|
60
|
+
def lang ; headers['Accept-Language'] ; end
|
61
|
+
def lang= val; headers['Accept-Language'] = val; end
|
62
|
+
|
63
|
+
def authorized? ; !!oauth_token ; end
|
64
|
+
|
65
|
+
# cookies, app_id, secrect related below
|
66
|
+
|
67
|
+
def parse_fbs! fbs
|
68
|
+
self.data = check_sig_and_return_data(
|
69
|
+
# take out facebook sometimes there but sometimes not quotes in cookies
|
70
|
+
Rack::Utils.parse_query(fbs.to_s.sub(/^"/, '').sub(/"$/, '')))
|
71
|
+
end
|
72
|
+
|
73
|
+
def fbs
|
74
|
+
"#{fbs_without_sig(data).join('&')}&sig=#{calculate_sig(data)}"
|
75
|
+
end
|
76
|
+
|
77
|
+
# facebook's new signed_request...
|
78
|
+
|
79
|
+
def parse_signed_request! request
|
80
|
+
sig_encoded, json_encoded = request.split('.')
|
81
|
+
sig, json = [sig_encoded, json_encoded].map{ |str|
|
82
|
+
"#{str.tr('-_', '+/')}==".unpack('m').first
|
83
|
+
}
|
84
|
+
self.data = check_sig_and_return_data(
|
85
|
+
JsonDecode.json_decode(json).merge('sig' => sig)){
|
86
|
+
Hmac.sha256(secret, json_encoded)
|
87
|
+
}
|
88
|
+
rescue JsonDecode::ParseError
|
89
|
+
self.data = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# oauth related
|
93
|
+
|
94
|
+
def authorize_url opts={}
|
95
|
+
url('oauth/authorize',
|
96
|
+
{:client_id => app_id, :access_token => nil}.merge(opts))
|
97
|
+
end
|
98
|
+
|
99
|
+
def authorize! opts={}
|
100
|
+
query = {:client_id => app_id, :client_secret => secret}.merge(opts)
|
101
|
+
self.data = Rack::Utils.parse_query(
|
102
|
+
request({:auto_decode => false}.merge(opts),
|
103
|
+
[:get, url('oauth/access_token', query)]))
|
104
|
+
end
|
105
|
+
|
106
|
+
# old rest facebook api, i will definitely love to remove them someday
|
107
|
+
|
108
|
+
def old_rest path, query={}, opts={}, &cb
|
109
|
+
uri = url("method/#{path}", {:format => 'json'}.merge(query),
|
110
|
+
{:site => old_site}.merge(opts))
|
111
|
+
request(opts, [:get, uri], &cb)
|
112
|
+
end
|
113
|
+
|
114
|
+
def secret_old_rest path, query={}, opts={}, &cb
|
115
|
+
old_rest(path, query, {:secret => true}.merge(opts), &cb)
|
116
|
+
end
|
117
|
+
|
118
|
+
def fql code, query={}, opts={}, &cb
|
119
|
+
old_rest('fql.query', {:query => code}.merge(query), opts, &cb)
|
120
|
+
end
|
121
|
+
|
122
|
+
def fql_multi codes, query={}, opts={}, &cb
|
123
|
+
old_rest('fql.multiquery',
|
124
|
+
{:queries => JsonDecode.json_encode(codes)}.merge(query), opts, &cb)
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
def check_sig_and_return_data cookies
|
129
|
+
cookies if secret && if block_given?
|
130
|
+
yield
|
131
|
+
else
|
132
|
+
calculate_sig(cookies)
|
133
|
+
end == cookies['sig']
|
134
|
+
end
|
135
|
+
|
136
|
+
def calculate_sig cookies
|
137
|
+
Digest::MD5.hexdigest(fbs_without_sig(cookies).join + secret)
|
138
|
+
end
|
139
|
+
|
140
|
+
def fbs_without_sig cookies
|
141
|
+
cookies.reject{ |(k, v)| k == 'sig' }.sort.map{ |a| a.join('=') }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
Facebook.send(:include, Facebook::Client)
|
data/example/github.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
# simple client
|
3
|
+
|
4
|
+
require 'rest-core'
|
5
|
+
|
6
|
+
RestCore::Builder.client('Github') do
|
7
|
+
s = self.class # this is only for ruby 1.8!
|
8
|
+
use s::Timeout , 10
|
9
|
+
|
10
|
+
use s::DefaultSite , 'https://api.github.com/'
|
11
|
+
use s::Oauth2Query , 'access_token', nil
|
12
|
+
|
13
|
+
use s::CommonLogger , method(:puts)
|
14
|
+
use s::Cache , {}, nil
|
15
|
+
use s::ErrorHandler , lambda{|env| raise env[s::RESPONSE_BODY]['message']}
|
16
|
+
use s::ErrorDetector, lambda{|env| env[s::RESPONSE_HEADERS]['status'].
|
17
|
+
first !~ /^2/}
|
18
|
+
use s::JsonDecode , true
|
19
|
+
|
20
|
+
run s::RestClient
|
21
|
+
end
|
data/lib/rest-core.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
module RestCore
|
3
|
+
REQUEST_METHOD = 'REQUEST_METHOD'
|
4
|
+
REQUEST_PATH = 'REQUEST_PATH'
|
5
|
+
REQUEST_QUERY = 'REQUEST_QUERY'
|
6
|
+
REQUEST_PAYLOAD = 'REQUEST_PAYLOAD'
|
7
|
+
REQUEST_HEADERS = 'REQUEST_HEADERS'
|
8
|
+
|
9
|
+
RESPONSE_BODY = 'RESPONSE_BODY'
|
10
|
+
RESPONSE_STATUS = 'RESPONSE_STATUS'
|
11
|
+
RESPONSE_HEADERS = 'RESPONSE_HEADERS'
|
12
|
+
|
13
|
+
ASK = 'core.ask'
|
14
|
+
FAIL = 'core.fail'
|
15
|
+
LOG = 'core.log'
|
16
|
+
|
17
|
+
# core utilities
|
18
|
+
autoload :Builder , 'rest-core/builder'
|
19
|
+
autoload :Client , 'rest-core/client'
|
20
|
+
autoload :Event , 'rest-core/event'
|
21
|
+
autoload :Middleware , 'rest-core/middleware'
|
22
|
+
autoload :Wrapper , 'rest-core/wrapper'
|
23
|
+
|
24
|
+
# oauth1 utilities
|
25
|
+
autoload :ClientOauth1 , 'rest-core/client_oauth1'
|
26
|
+
|
27
|
+
# misc utilities
|
28
|
+
autoload :Hmac , 'rest-core/util/hmac'
|
29
|
+
|
30
|
+
# middlewares
|
31
|
+
autoload :Cache , 'rest-core/middleware/cache'
|
32
|
+
autoload :CommonLogger , 'rest-core/middleware/common_logger'
|
33
|
+
autoload :DefaultHeaders, 'rest-core/middleware/default_headers'
|
34
|
+
autoload :DefaultQuery , 'rest-core/middleware/default_query'
|
35
|
+
autoload :DefaultSite , 'rest-core/middleware/default_site'
|
36
|
+
autoload :Defaults , 'rest-core/middleware/defaults'
|
37
|
+
autoload :ErrorDetector , 'rest-core/middleware/error_detector'
|
38
|
+
autoload :ErrorDetectorHttp, 'rest-core/middleware/error_detector_http'
|
39
|
+
autoload :ErrorHandler , 'rest-core/middleware/error_handler'
|
40
|
+
autoload :JsonDecode , 'rest-core/middleware/json_decode'
|
41
|
+
autoload :Oauth1Header , 'rest-core/middleware/oauth1_header'
|
42
|
+
autoload :Oauth2Query , 'rest-core/middleware/oauth2_query'
|
43
|
+
autoload :Timeout , 'rest-core/middleware/timeout'
|
44
|
+
|
45
|
+
# apps
|
46
|
+
autoload :Ask , 'rest-core/app/ask'
|
47
|
+
autoload :RestClient , 'rest-core/app/rest-client'
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/middleware'
|
3
|
+
|
4
|
+
require 'restclient'
|
5
|
+
|
6
|
+
class RestCore::RestClient
|
7
|
+
include RestCore::Middleware
|
8
|
+
def call env
|
9
|
+
respond(env,
|
10
|
+
::RestClient::Request.execute(:method => env[REQUEST_METHOD ],
|
11
|
+
:url => request_uri(env) ,
|
12
|
+
:payload => env[REQUEST_PAYLOAD],
|
13
|
+
:headers => env[REQUEST_HEADERS]))
|
14
|
+
|
15
|
+
rescue ::RestClient::Exception => e
|
16
|
+
respond(env, e.response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond env, response
|
20
|
+
env.merge(RESPONSE_BODY => response.body,
|
21
|
+
RESPONSE_STATUS => response.code,
|
22
|
+
RESPONSE_HEADERS => response.raw_headers)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/client'
|
3
|
+
require 'rest-core/wrapper'
|
4
|
+
|
5
|
+
class RestCore::Builder
|
6
|
+
include RestCore
|
7
|
+
include Wrapper
|
8
|
+
|
9
|
+
def self.client prefix, *attrs, &block
|
10
|
+
new(&block).to_client(prefix, *attrs)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_client prefix, *attrs
|
14
|
+
# struct = Struct.new(*members, *attrs) if RUBY_VERSION >= 1.9.2
|
15
|
+
struct = Struct.new(*(members + attrs))
|
16
|
+
client = Class.new(struct)
|
17
|
+
client.send(:include, Client)
|
18
|
+
Object.const_set( prefix , client)
|
19
|
+
client.const_set('Struct', struct)
|
20
|
+
class << client; attr_reader :builder; end
|
21
|
+
client.instance_variable_set(:@builder, self)
|
22
|
+
client
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
|
2
|
+
require 'rest-core'
|
3
|
+
|
4
|
+
module RestCore::Client
|
5
|
+
include RestCore
|
6
|
+
|
7
|
+
Unserializable = [Proc, Method, IO]
|
8
|
+
|
9
|
+
def self.included mod
|
10
|
+
# honor default attributes
|
11
|
+
src = mod.members.map{ |name|
|
12
|
+
<<-RUBY
|
13
|
+
def #{name}
|
14
|
+
if (r = super).nil?
|
15
|
+
self.#{name} = default_#{name}
|
16
|
+
else
|
17
|
+
r
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_#{name} app=app
|
22
|
+
if app.respond_to?(:#{name})
|
23
|
+
app.#{name}({})
|
24
|
+
elsif app.respond_to?(:wrapped)
|
25
|
+
default_#{name}(app.wrapped) ||
|
26
|
+
default_#{name}(app.app)
|
27
|
+
elsif app.respond_to?(:app)
|
28
|
+
default_#{name}(app.app)
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
private :default_#{name}
|
34
|
+
|
35
|
+
self
|
36
|
+
RUBY
|
37
|
+
}
|
38
|
+
# if RUBY_VERSION < '1.9.2'
|
39
|
+
src << <<-RUBY if mod.members.first.kind_of?(String)
|
40
|
+
def members
|
41
|
+
super.map(&:to_sym)
|
42
|
+
end
|
43
|
+
self
|
44
|
+
RUBY
|
45
|
+
# end
|
46
|
+
accessor = Module.new.module_eval(src.join("\n"), __FILE__, __LINE__)
|
47
|
+
mod.const_set('Accessor', accessor)
|
48
|
+
mod.send(:include, accessor)
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :app, :ask
|
52
|
+
def initialize o={}
|
53
|
+
@app ||= self.class.builder.to_app
|
54
|
+
@ask ||= self.class.builder.to_app(Ask)
|
55
|
+
o.each{ |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
|
56
|
+
end
|
57
|
+
|
58
|
+
def attributes
|
59
|
+
Hash[each_pair.map{ |k, v| [k, send(k)] }]
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
"#<struct #{self.class.name} #{attributes.map{ |k, v|
|
64
|
+
"#{k}=#{v.inspect}" }.join(', ')}>"
|
65
|
+
end
|
66
|
+
|
67
|
+
def lighten! o={}
|
68
|
+
attributes.each{ |k, v| vv = case v;
|
69
|
+
when Hash; lighten_hash(v)
|
70
|
+
when Array; lighten_array(v)
|
71
|
+
when *Unserializable; false
|
72
|
+
else v
|
73
|
+
end
|
74
|
+
send("#{k}=", vv)}
|
75
|
+
initialize(o)
|
76
|
+
@app, @ask = lighten_app(app), lighten_app(ask)
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def lighten o={}
|
81
|
+
dup.lighten!(o)
|
82
|
+
end
|
83
|
+
|
84
|
+
def url path, query={}, opts={}
|
85
|
+
Middleware.request_uri(
|
86
|
+
ask.call(build_env({
|
87
|
+
REQUEST_PATH => path,
|
88
|
+
REQUEST_QUERY => query,
|
89
|
+
ASK => true}.merge(opts))))
|
90
|
+
end
|
91
|
+
|
92
|
+
# extra options:
|
93
|
+
# auto_decode: Bool # decode with json or not in this API request
|
94
|
+
# # default: auto_decode in rest-graph instance
|
95
|
+
# timeout: Int # the timeout for this API request
|
96
|
+
# # default: timeout in rest-graph instance
|
97
|
+
# secret: Bool # use secret_acccess_token or not
|
98
|
+
# # default: false
|
99
|
+
# cache: Bool # use cache or not; if it's false, update cache, too
|
100
|
+
# # default: true
|
101
|
+
# expires_in: Int # control when would the cache be expired
|
102
|
+
# # default: nil
|
103
|
+
# async: Bool # use eventmachine for http client or not
|
104
|
+
# # default: false, but true in aget family
|
105
|
+
# headers: Hash # additional hash you want to pass
|
106
|
+
# # default: {}
|
107
|
+
def get path, query={}, opts={}, &cb
|
108
|
+
request(opts, [:get , path, query], &cb)
|
109
|
+
end
|
110
|
+
|
111
|
+
def delete path, query={}, opts={}, &cb
|
112
|
+
request(opts, [:delete, path, query], &cb)
|
113
|
+
end
|
114
|
+
|
115
|
+
def post path, payload={}, query={}, opts={}, &cb
|
116
|
+
request(opts, [:post , path, query, payload], &cb)
|
117
|
+
end
|
118
|
+
|
119
|
+
def put path, payload={}, query={}, opts={}, &cb
|
120
|
+
request(opts, [:put , path, query, payload], &cb)
|
121
|
+
end
|
122
|
+
|
123
|
+
# request by eventmachine (em-http)
|
124
|
+
|
125
|
+
def aget path, query={}, opts={}, &cb
|
126
|
+
get(path, query, {:async => true}.merge(opts), &cb)
|
127
|
+
end
|
128
|
+
|
129
|
+
def adelete path, query={}, opts={}, &cb
|
130
|
+
delete(path, query, {:async => true}.merge(opts), &cb)
|
131
|
+
end
|
132
|
+
|
133
|
+
def apost path, payload={}, query={}, opts={}, &cb
|
134
|
+
post(path, payload, query, {:async => true}.merge(opts), &cb)
|
135
|
+
end
|
136
|
+
|
137
|
+
def aput path, payload={}, query={}, opts={}, &cb
|
138
|
+
put(path, payload, query, {:async => true}.merge(opts), &cb)
|
139
|
+
end
|
140
|
+
|
141
|
+
def multi reqs, opts={}, &cb
|
142
|
+
request({:async => true}.merge(opts), *reqs, &cb)
|
143
|
+
end
|
144
|
+
|
145
|
+
def request opts, *reqs
|
146
|
+
req = reqs.first
|
147
|
+
response = app.call(build_env({
|
148
|
+
REQUEST_METHOD => req[0] ,
|
149
|
+
REQUEST_PATH => req[1] ,
|
150
|
+
REQUEST_QUERY => req[2] ,
|
151
|
+
REQUEST_PAYLOAD => req[3] ,
|
152
|
+
REQUEST_HEADERS => opts['headers'],
|
153
|
+
FAIL => [] ,
|
154
|
+
LOG => []}.merge(opts)))[RESPONSE_BODY]
|
155
|
+
|
156
|
+
if block_given?
|
157
|
+
yield(response)
|
158
|
+
else
|
159
|
+
response
|
160
|
+
end
|
161
|
+
end
|
162
|
+
# ------------------------ instance ---------------------
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
protected
|
167
|
+
def build_env env={}
|
168
|
+
string_keys(attributes).merge(string_keys(env))
|
169
|
+
end
|
170
|
+
|
171
|
+
def string_keys hash
|
172
|
+
hash.inject({}){ |r, (k, v)|
|
173
|
+
if v.kind_of?(Hash)
|
174
|
+
r[k.to_s] = case k.to_s
|
175
|
+
when REQUEST_QUERY, REQUEST_PAYLOAD, REQUEST_HEADERS
|
176
|
+
string_keys(v)
|
177
|
+
else; v
|
178
|
+
end
|
179
|
+
else
|
180
|
+
r[k.to_s] = v
|
181
|
+
end
|
182
|
+
r
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def lighten_hash hash
|
187
|
+
Hash[hash.map{ |(key, value)|
|
188
|
+
case value
|
189
|
+
when Hash; lighten_hash(value)
|
190
|
+
when Array; lighten_array(value)
|
191
|
+
when *Unserializable; [key, nil]
|
192
|
+
else [key, value]
|
193
|
+
end
|
194
|
+
}]
|
195
|
+
end
|
196
|
+
|
197
|
+
def lighten_array array
|
198
|
+
array.map{ |value|
|
199
|
+
case value
|
200
|
+
when Hash; lighten_hash(value)
|
201
|
+
when Array; lighten_array(value)
|
202
|
+
when *Unserializable; nil
|
203
|
+
else value
|
204
|
+
end
|
205
|
+
}.compact
|
206
|
+
end
|
207
|
+
|
208
|
+
def lighten_app app
|
209
|
+
members = if app.class.respond_to?(:members)
|
210
|
+
app.class.members.map{ |key|
|
211
|
+
case value = app.send(key, {})
|
212
|
+
when Hash; lighten_hash(value)
|
213
|
+
when Array; lighten_array(value)
|
214
|
+
when *Unserializable; nil
|
215
|
+
else value
|
216
|
+
end
|
217
|
+
}
|
218
|
+
else
|
219
|
+
[]
|
220
|
+
end
|
221
|
+
|
222
|
+
if app.respond_to?(:app) && app.app
|
223
|
+
wrapped = if app.respond_to?(:wrapped) && app.wrapped
|
224
|
+
lighten_app(app.wrapped)
|
225
|
+
else
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
app.class.new(lighten_app(app.app), *members){
|
229
|
+
@wrapped = wrapped if wrapped
|
230
|
+
}
|
231
|
+
else
|
232
|
+
app.class.new(*members)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
def request_em opts, reqs
|
238
|
+
start_time = Time.now
|
239
|
+
rs = reqs.map{ |(meth, path, query, payload)|
|
240
|
+
r = EM::HttpRequest.new(path).send(meth, :body => payload,
|
241
|
+
:head => build_headers(opts),
|
242
|
+
:query => query)
|
243
|
+
if cached = cache_get(opts, path)
|
244
|
+
# TODO: this is hack!!
|
245
|
+
r.instance_variable_set('@response', cached)
|
246
|
+
r.instance_variable_set('@state' , :finish)
|
247
|
+
r.on_request_complete
|
248
|
+
r.succeed(r)
|
249
|
+
else
|
250
|
+
r.callback{
|
251
|
+
cache_for(opts, path, meth, r.response)
|
252
|
+
log(env.merge('event' =>
|
253
|
+
Event::Requested.new(Time.now - start_time, path)))
|
254
|
+
}
|
255
|
+
r.error{
|
256
|
+
log(env.merge('event' =>
|
257
|
+
Event::Failed.new(Time.now - start_time, path)))
|
258
|
+
}
|
259
|
+
end
|
260
|
+
r
|
261
|
+
}
|
262
|
+
EM::MultiRequest.new(rs){ |m|
|
263
|
+
# TODO: how to deal with the failed?
|
264
|
+
clients = m.responses[:succeeded]
|
265
|
+
results = clients.map{ |client|
|
266
|
+
post_request(opts, client.uri, client.response)
|
267
|
+
}
|
268
|
+
|
269
|
+
if reqs.size == 1
|
270
|
+
yield(results.first)
|
271
|
+
else
|
272
|
+
log(env.merge('event' => Event::MultiDone.new(Time.now - start_time,
|
273
|
+
clients.map(&:uri).join(', '))))
|
274
|
+
yield(results)
|
275
|
+
end
|
276
|
+
}
|
277
|
+
end
|
278
|
+
end
|