rest-core 1.0.3 → 2.0.0

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 (87) hide show
  1. data/.travis.yml +6 -7
  2. data/CHANGES.md +137 -0
  3. data/Gemfile +1 -1
  4. data/README.md +183 -191
  5. data/TODO.md +5 -8
  6. data/example/multi.rb +31 -24
  7. data/example/simple.rb +28 -0
  8. data/example/use-cases.rb +194 -0
  9. data/lib/rest-core.rb +26 -19
  10. data/lib/rest-core/builder.rb +2 -2
  11. data/lib/rest-core/client.rb +40 -27
  12. data/lib/rest-core/client/universal.rb +16 -13
  13. data/lib/rest-core/client_oauth1.rb +5 -5
  14. data/lib/rest-core/engine/auto.rb +25 -0
  15. data/lib/rest-core/{app → engine}/dry.rb +1 -2
  16. data/lib/rest-core/engine/em-http-request.rb +39 -0
  17. data/lib/rest-core/engine/future/future.rb +106 -0
  18. data/lib/rest-core/engine/future/future_fiber.rb +39 -0
  19. data/lib/rest-core/engine/future/future_thread.rb +29 -0
  20. data/lib/rest-core/engine/rest-client.rb +56 -0
  21. data/lib/rest-core/middleware.rb +27 -5
  22. data/lib/rest-core/middleware/auth_basic.rb +5 -5
  23. data/lib/rest-core/middleware/bypass.rb +2 -2
  24. data/lib/rest-core/middleware/cache.rb +67 -54
  25. data/lib/rest-core/middleware/common_logger.rb +5 -8
  26. data/lib/rest-core/middleware/default_headers.rb +2 -2
  27. data/lib/rest-core/middleware/default_payload.rb +26 -2
  28. data/lib/rest-core/middleware/default_query.rb +4 -2
  29. data/lib/rest-core/middleware/default_site.rb +8 -6
  30. data/lib/rest-core/middleware/error_detector.rb +9 -16
  31. data/lib/rest-core/middleware/error_handler.rb +25 -11
  32. data/lib/rest-core/middleware/follow_redirect.rb +11 -14
  33. data/lib/rest-core/middleware/json_request.rb +19 -0
  34. data/lib/rest-core/middleware/json_response.rb +28 -0
  35. data/lib/rest-core/middleware/oauth1_header.rb +2 -7
  36. data/lib/rest-core/middleware/oauth2_header.rb +4 -7
  37. data/lib/rest-core/middleware/oauth2_query.rb +2 -2
  38. data/lib/rest-core/middleware/timeout.rb +21 -65
  39. data/lib/rest-core/middleware/timeout/{eventmachine_timer.rb → timer_em.rb} +3 -1
  40. data/lib/rest-core/middleware/timeout/timer_thread.rb +36 -0
  41. data/lib/rest-core/patch/multi_json.rb +8 -0
  42. data/lib/rest-core/test.rb +3 -12
  43. data/lib/rest-core/util/json.rb +65 -0
  44. data/lib/rest-core/util/parse_query.rb +2 -2
  45. data/lib/rest-core/version.rb +1 -1
  46. data/lib/rest-core/wrapper.rb +16 -16
  47. data/rest-core.gemspec +28 -27
  48. data/test/test_auth_basic.rb +14 -10
  49. data/test/test_builder.rb +7 -7
  50. data/test/test_cache.rb +126 -37
  51. data/test/test_client.rb +3 -1
  52. data/test/test_client_oauth1.rb +2 -3
  53. data/test/test_default_query.rb +17 -23
  54. data/test/test_em_http_request.rb +146 -0
  55. data/test/test_error_detector.rb +0 -1
  56. data/test/test_error_handler.rb +44 -0
  57. data/test/test_follow_redirect.rb +17 -19
  58. data/test/test_json_request.rb +28 -0
  59. data/test/test_json_response.rb +51 -0
  60. data/test/test_oauth1_header.rb +4 -4
  61. data/test/test_payload.rb +20 -12
  62. data/test/test_simple.rb +14 -0
  63. data/test/test_timeout.rb +11 -19
  64. data/test/test_universal.rb +5 -5
  65. data/test/test_wrapper.rb +19 -13
  66. metadata +28 -29
  67. data/doc/ToC.md +0 -7
  68. data/doc/dependency.md +0 -4
  69. data/doc/design.md +0 -4
  70. data/example/auto.rb +0 -51
  71. data/example/coolio.rb +0 -21
  72. data/example/eventmachine.rb +0 -30
  73. data/example/rest-client.rb +0 -16
  74. data/lib/rest-core/app/abstract/async_fiber.rb +0 -13
  75. data/lib/rest-core/app/auto.rb +0 -23
  76. data/lib/rest-core/app/coolio-async.rb +0 -32
  77. data/lib/rest-core/app/coolio-fiber.rb +0 -30
  78. data/lib/rest-core/app/coolio.rb +0 -9
  79. data/lib/rest-core/app/em-http-request-async.rb +0 -37
  80. data/lib/rest-core/app/em-http-request-fiber.rb +0 -45
  81. data/lib/rest-core/app/em-http-request.rb +0 -9
  82. data/lib/rest-core/app/rest-client.rb +0 -41
  83. data/lib/rest-core/middleware/json_decode.rb +0 -93
  84. data/lib/rest-core/middleware/timeout/coolio_timer.rb +0 -10
  85. data/pending/test_multi.rb +0 -123
  86. data/pending/test_test_util.rb +0 -86
  87. data/test/test_json_decode.rb +0 -24
data/TODO.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # TODO
2
2
 
3
- * HTTP method in Requested log
4
3
  * middleware revisit (how to initialize?)
5
- * test utility
4
+ * streaming?
5
+ * connection pool?
6
+ * X-Method-Override
6
7
 
7
8
  # BUG
8
9
 
@@ -11,10 +12,6 @@
11
12
  # FEATURE
12
13
 
13
14
  * middleware composer
14
- * RC::Payload which can handle Content-Type: application/json
15
- * how to pass a arbitrary payload generator?
16
- * RC::JsonRequest
17
- * RC::FakeGetWithPayload
15
+ * headers and payload logs for CommonLogger
18
16
 
19
- * ResponseThunk and EmHttpRequestThunk
20
- * options for apps
17
+ # rest-request
@@ -1,35 +1,42 @@
1
1
 
2
+ require 'fiber'
3
+ require 'em-http-request'
2
4
  require 'rest-core'
3
- require 'eventmachine'
4
- RestCore::EmHttpRequest # there might be a autoload bug?
5
- # omitting this line would cause
6
- # stack level too deep (SystemStackError)
7
5
 
8
- YourClient = RestCore::Builder.client do
9
- s = RestCore
10
- use s::DefaultSite , 'https://api.github.com/users/'
11
- use s::JsonDecode , true
12
- use s::CommonLogger, method(:puts)
13
- use s::Cache , nil, 3600
14
- run s::Auto
6
+ YourClient = RC::Builder.client do
7
+ use RC::DefaultSite , 'https://api.github.com/users/'
8
+ use RC::JsonResponse, true
9
+ use RC::CommonLogger, method(:puts)
10
+ use RC::Cache , nil, 3600
15
11
  end
16
12
 
17
13
  client = YourClient.new
14
+ puts "rest-client with threads doing concurrent requests"
15
+ a = [client.get('cardinalblue')['name'], client.get('godfat')['name']]
16
+ puts "It's not blocking... but doing concurrent requests underneath"
17
+ p a # here we want the values, so it blocks here
18
+ puts "DONE"
19
+
20
+ puts; puts
21
+
22
+ puts "eventmachine with threads doing concurrent requests"
23
+ EM.run{
24
+ Thread.new{
25
+ p [client.get('cardinalblue')['name'], client.get('godfat')['name']]
26
+ puts "DONE"
27
+ EM.stop
28
+ }
29
+ puts "It's not blocking... but doing concurrent requests underneath"
30
+ }
31
+
32
+ puts; puts
33
+
34
+ puts "eventmachine with fibers doing concurrent requests"
18
35
  EM.run{
19
36
  Fiber.new{
20
- fiber = Fiber.current
21
- result = {}
22
- client.get('cardinalblue'){ |response|
23
- result[0] = response
24
- fiber.resume(result) if result.size == 2
25
- }
26
- puts "It's not blocking..."
27
- client.get('cardinalblue'){ |response|
28
- result[1] = response
29
- fiber.resume(result) if result.size == 2
30
- }
31
- p Fiber.yield
37
+ p [client.get('cardinalblue')['name'], client.get('godfat')['name']]
38
+ puts "DONE"
32
39
  EM.stop
33
40
  }.resume
34
- puts "It's not blocking..."
41
+ puts "It's not blocking... but doing concurrent requests underneath"
35
42
  }
@@ -0,0 +1,28 @@
1
+
2
+ require 'rest-core'
3
+
4
+ YourClient = RC::Builder.client do
5
+ use RC::DefaultSite , 'https://api.github.com/users/'
6
+ use RC::JsonResponse, true
7
+ use RC::CommonLogger, method(:puts)
8
+ use RC::Cache , nil, 3600
9
+ end
10
+
11
+ client = YourClient.new(:cache => {})
12
+ p client.get('cardinalblue') # cache miss
13
+ puts
14
+ p client.get('cardinalblue') # cache hit
15
+
16
+ client.cache = false
17
+
18
+ puts "concurrent requests"
19
+ a = [client.get('cardinalblue')['name'], client.get('godfat')['name']]
20
+ puts "It's not blocking... but doing concurrent requests underneath"
21
+ p a # here we want the values, so it blocks here
22
+ puts "DONE"
23
+
24
+ puts "callback"
25
+ client.get('cardinalblue'){ |v| p v }
26
+ puts "It's not blocking... but doing concurrent requests underneath"
27
+ client.wait # we block here to wait for the request done
28
+ puts "DONE"
@@ -0,0 +1,194 @@
1
+
2
+ require 'fiber'
3
+ require 'em-http-request'
4
+ require 'rest-core'
5
+ RC.eagerload
6
+
7
+ def def_use_case name, &block
8
+ singleton_class.send(:define_method, "#{name}_", &block)
9
+ singleton_class.send(:define_method, name) do
10
+ @count ||= 0
11
+ printf "Use case #%02d: %s\n", @count+=1, name
12
+ puts '-' * 70
13
+ start = Time.now
14
+ send("#{name}_")
15
+ puts "Spent #{Time.now - start} seconds for this use case."
16
+ puts
17
+ end
18
+ end
19
+
20
+ def q str, m=nil
21
+ p = lambda{ puts "\e[33m=> #{str.inspect}\e[0m" }
22
+ if m
23
+ m.synchronize(&p)
24
+ else
25
+ p.call
26
+ end
27
+ end
28
+
29
+ # ----------------------------------------------------------------------
30
+
31
+ def_use_case 'pure_ruby_single_request' do
32
+ q RC::Universal.new(:json_response => true).
33
+ get('https://api.github.com/users/godfat')['name']
34
+ end
35
+
36
+ def_use_case 'pure_ruby_concurrent_requests' do
37
+ client = RC::Universal.new(:json_response => true,
38
+ :site => 'https://api.github.com/users/')
39
+ q [client.get('godfat'), client.get('cardinalblue')].map{ |u| u['name'] }
40
+ end
41
+
42
+ def_use_case 'pure_ruby_cache_requests' do
43
+ client = RC::Universal.new(:json_response => true, :cache => {})
44
+ 3.times{ q client.get('https://api.github.com/users/godfat')['name'] }
45
+ end
46
+
47
+ def_use_case 'pure_ruby_callback_requests' do
48
+ m = Mutex.new
49
+ RC::Universal.new(:json_response => true ,
50
+ :site => 'https://api.github.com/users/' ,
51
+ :log_method => lambda{|str| m.synchronize{puts(str)}}).
52
+ get('godfat'){ |res|
53
+ q res['name'], m
54
+ }.
55
+ get('cardinalblue'){ |res|
56
+ q res['name'], m
57
+ }.wait
58
+ end
59
+
60
+ def_use_case 'pure_ruby_nested_concurrent_requests' do
61
+ m = Mutex.new
62
+ c = RC::Universal.new(:json_response => true ,
63
+ :site => 'https://api.github.com' ,
64
+ :log_method => lambda{|str| m.synchronize{puts(str)}})
65
+
66
+ %w[rubytaiwan godfat].each{ |user|
67
+ c.get("/users/#{user}/repos", :per_page => 100){ |repos|
68
+ rs = repos.reject{ |r| r['fork'] }
69
+ most_watched = rs.max_by{ |r| r['watchers'] }['name']
70
+ most_size = rs.max_by{ |r| r['size'] }['name']
71
+
72
+ watch_contri = c.get("/repos/#{user}/#{most_watched}/contributors")
73
+ size_contri = c.get("/repos/#{user}/#{most_size}/contributors")
74
+
75
+ most_watched_most_contri = watch_contri.max_by{ |c| c['contributions'] }
76
+ most_size_most_contri = size_contri.max_by{ |c| c['contributions'] }
77
+
78
+ q "Most contributed user for most watched: #{user}/#{most_watched}:", m
79
+ q most_watched_most_contri['login'], m
80
+
81
+ q "Most contributed user for most size : #{user}/#{most_size}:", m
82
+ q most_size_most_contri['login'], m
83
+ }
84
+ }
85
+
86
+ c.wait
87
+ end
88
+
89
+ # ----------------------------------------------------------------------
90
+
91
+ def_use_case 'eventmachine_fiber_single_request' do
92
+ EM.run{ Fiber.new{ pure_ruby_single_request_ ; EM.stop }.resume}
93
+ end
94
+
95
+ def_use_case 'eventmachine_fiber_concurrent_requests' do
96
+ EM.run{ Fiber.new{ pure_ruby_concurrent_requests_ ; EM.stop }.resume}
97
+ end
98
+
99
+ def_use_case 'eventmachine_fiber_cache_requests' do
100
+ EM.run{ Fiber.new{ pure_ruby_cache_requests_ ; EM.stop }.resume}
101
+ end
102
+
103
+ def_use_case 'eventmachine_fiber_callback_requests' do
104
+ EM.run{ Fiber.new{ pure_ruby_callback_requests_ ; EM.stop }.resume}
105
+ end
106
+
107
+ def_use_case 'eventmachine_fiber_nested_concurrent_requests' do
108
+ EM.run{ Fiber.new{ pure_ruby_nested_concurrent_requests_; EM.stop }.resume}
109
+ end
110
+
111
+ # ----------------------------------------------------------------------
112
+
113
+ def_use_case 'eventmachine_thread_single_request' do
114
+ EM.run{ Thread.new{ pure_ruby_single_request_ ; EM.stop } }
115
+ end
116
+
117
+ def_use_case 'eventmachine_thread_concurrent_requests' do
118
+ EM.run{ Thread.new{ pure_ruby_concurrent_requests_ ; EM.stop } }
119
+ end
120
+
121
+ def_use_case 'eventmachine_thread_cache_requests' do
122
+ EM.run{ Thread.new{ pure_ruby_cache_requests_ ; EM.stop } }
123
+ end
124
+
125
+ def_use_case 'eventmachine_thread_callback_requests' do
126
+ EM.run{ Thread.new{ pure_ruby_callback_requests_ ; EM.stop } }
127
+ end
128
+
129
+ def_use_case 'eventmachine_thread_nested_concurrent_requests' do
130
+ EM.run{ Thread.new{ pure_ruby_nested_concurrent_requests_; EM.stop } }
131
+ end
132
+
133
+ # ----------------------------------------------------------------------
134
+
135
+ def_use_case 'eventmachine_rest_client_single_request' do
136
+ EM.run{ pure_ruby_single_request_ ; EM.stop }
137
+ end
138
+
139
+ def_use_case 'eventmachine_rest_client_concurrent_requests' do
140
+ EM.run{ pure_ruby_concurrent_requests_ ; EM.stop }
141
+ end
142
+
143
+ def_use_case 'eventmachine_rest_client_cache_requests' do
144
+ EM.run{ pure_ruby_cache_requests_ ; EM.stop }
145
+ end
146
+
147
+ def_use_case 'eventmachine_rest_client_callback_requests' do
148
+ EM.run{ pure_ruby_callback_requests_ ; EM.stop }
149
+ end
150
+
151
+ def_use_case 'eventmachine_rest_client_nested_concurrent_requests' do
152
+ EM.run{ pure_ruby_nested_concurrent_requests_; EM.stop }
153
+ end
154
+
155
+ # ----------------------------------------------------------------------
156
+
157
+ def_use_case 'pure_ruby' do
158
+ pure_ruby_single_request
159
+ pure_ruby_concurrent_requests
160
+ pure_ruby_cache_requests
161
+ pure_ruby_callback_requests
162
+ pure_ruby_nested_concurrent_requests
163
+ end
164
+
165
+ def_use_case 'eventmachine_fiber' do
166
+ eventmachine_fiber_single_request
167
+ eventmachine_fiber_concurrent_requests
168
+ eventmachine_fiber_cache_requests
169
+ eventmachine_fiber_callback_requests
170
+ eventmachine_fiber_nested_concurrent_requests
171
+ end
172
+
173
+ def_use_case 'eventmachine_thread' do
174
+ eventmachine_thread_single_request
175
+ eventmachine_thread_concurrent_requests
176
+ eventmachine_thread_cache_requests
177
+ eventmachine_thread_callback_requests
178
+ eventmachine_thread_nested_concurrent_requests
179
+ end
180
+
181
+ def_use_case 'eventmachine_rest_client' do
182
+ eventmachine_rest_client_single_request
183
+ eventmachine_rest_client_concurrent_requests
184
+ eventmachine_rest_client_cache_requests
185
+ eventmachine_rest_client_callback_requests
186
+ eventmachine_rest_client_nested_concurrent_requests
187
+ end
188
+
189
+ # ----------------------------------------------------------------------
190
+
191
+ pure_ruby
192
+ eventmachine_fiber
193
+ eventmachine_thread
194
+ eventmachine_rest_client
@@ -16,6 +16,9 @@ module RestCore
16
16
 
17
17
  ASYNC = 'async.callback'
18
18
  TIMER = 'async.timer'
19
+ FUTURE = 'async.future'
20
+
21
+ RootFiber = Fiber.respond_to?(:current) && Fiber.current
19
22
 
20
23
  # core utilities
21
24
  autoload :Builder , 'rest-core/builder'
@@ -46,34 +49,38 @@ module RestCore
46
49
  autoload :ErrorDetectorHttp, 'rest-core/middleware/error_detector_http'
47
50
  autoload :ErrorHandler , 'rest-core/middleware/error_handler'
48
51
  autoload :FollowRedirect, 'rest-core/middleware/follow_redirect'
49
- autoload :JsonDecode , 'rest-core/middleware/json_decode'
52
+ autoload :JsonRequest , 'rest-core/middleware/json_request'
53
+ autoload :JsonResponse , 'rest-core/middleware/json_response'
50
54
  autoload :Oauth1Header , 'rest-core/middleware/oauth1_header'
51
55
  autoload :Oauth2Header , 'rest-core/middleware/oauth2_header'
52
56
  autoload :Oauth2Query , 'rest-core/middleware/oauth2_query'
53
57
  autoload :Timeout , 'rest-core/middleware/timeout'
54
58
 
55
- # apps
56
- autoload :Auto , 'rest-core/app/auto'
57
- autoload :Dry , 'rest-core/app/dry'
58
- autoload :RestClient , 'rest-core/app/rest-client'
59
- autoload :Coolio , 'rest-core/app/coolio'
60
- autoload :CoolioAsync , 'rest-core/app/coolio-async'
61
- autoload :CoolioFiber , 'rest-core/app/coolio-fiber'
62
- autoload :EmHttpRequest , 'rest-core/app/em-http-request'
63
- autoload :EmHttpRequestAsync, 'rest-core/app/em-http-request-async'
64
- autoload :EmHttpRequestFiber, 'rest-core/app/em-http-request-fiber'
59
+ # engines
60
+ autoload :Auto , 'rest-core/engine/auto'
61
+ autoload :Dry , 'rest-core/engine/dry'
62
+ autoload :RestClient , 'rest-core/engine/rest-client'
63
+ autoload :EmHttpRequest , 'rest-core/engine/em-http-request'
65
64
 
66
65
  # clients
67
66
  autoload :Simple , 'rest-core/client/simple'
68
67
  autoload :Universal , 'rest-core/client/universal'
68
+
69
+ # You might want to call this before launching your application in a
70
+ # threaded environment to avoid thread-safety issue in autoload.
71
+ def self.eagerload const=self, loaded={}
72
+ return if loaded[const.name]
73
+ loaded[const.name] = true
74
+ const.constants.each{ |n|
75
+ begin
76
+ c = const.const_get(n)
77
+ rescue LoadError => e
78
+ warn "RestCore: WARN: #{e} for #{const}\n" \
79
+ " from #{e.backtrace.grep(/top.+required/).first}"
80
+ end
81
+ eagerload(c, loaded) if c.respond_to?(:constants) && !loaded[n]
82
+ }
83
+ end
69
84
  end
70
85
 
71
86
  RC = RestCore unless Object.const_defined?(:RC)
72
-
73
- begin
74
- require 'fiber'
75
- rescue LoadError
76
- end
77
- # assume we would always require 'rest-core' in root fiber
78
- RestCore::RootFiber = Fiber.current if Object.const_defined?(:Fiber) &&
79
- Fiber.respond_to?(:current)
@@ -6,8 +6,8 @@ class RestCore::Builder
6
6
  include RestCore
7
7
  include Wrapper
8
8
 
9
- def self.default_app
10
- @default_app ||= RestClient
9
+ def self.default_engine
10
+ @default_engine ||= RestCore::Auto
11
11
  end
12
12
 
13
13
  def self.client *attrs, &block
@@ -1,6 +1,8 @@
1
1
 
2
2
  require 'rest-core'
3
3
 
4
+ require 'weakref'
5
+
4
6
  module RestCore::Client
5
7
  include RestCore
6
8
 
@@ -48,10 +50,12 @@ module RestCore::Client
48
50
  mod.send(:include, accessor)
49
51
  end
50
52
 
51
- attr_reader :app, :dry
53
+ attr_reader :app, :dry, :futures
52
54
  def initialize o={}
53
- @app ||= self.class.builder.to_app
55
+ @app ||= self.class.builder.to_app # lighten! would reinitialize anyway
54
56
  @dry ||= self.class.builder.to_app(Dry)
57
+ @futures = [] # don't record any futures in lighten!
58
+ @mutex = nil # for locking futures, lazily initialized for serialization
55
59
  o.each{ |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
56
60
  end
57
61
 
@@ -81,12 +85,27 @@ module RestCore::Client
81
85
  dup.lighten!(o)
82
86
  end
83
87
 
88
+ def wait
89
+ return self if futures.empty?
90
+ current_futures = nil
91
+ mutex.synchronize{
92
+ current_futures = futures.dup
93
+ futures.clear
94
+ }
95
+ current_futures.each{ |f|
96
+ begin
97
+ f.wait
98
+ rescue WeakRef::RefError # it's gc'ed after we think it's alive
99
+ end if f.weakref_alive?
100
+ }
101
+ wait
102
+ end
103
+
84
104
  def url path, query={}, opts={}
85
- Middleware.request_uri(
86
- dry.call(build_env({
87
- REQUEST_PATH => path,
88
- REQUEST_QUERY => query,
89
- DRY => true}.merge(opts))))
105
+ dry.call(build_env({
106
+ REQUEST_PATH => path,
107
+ REQUEST_QUERY => query,
108
+ DRY => true}.merge(opts)), &Middleware.method(:request_uri))
90
109
  end
91
110
 
92
111
  def get path, query={}, opts={}, &cb
@@ -151,7 +170,7 @@ module RestCore::Client
151
170
  end
152
171
  end
153
172
 
154
- def request_full env, app=app
173
+ def request_full env, app=app, &k
155
174
  response = app.call(build_env(
156
175
  {REQUEST_METHOD => :get,
157
176
  REQUEST_PATH => '/' ,
@@ -160,11 +179,16 @@ module RestCore::Client
160
179
  REQUEST_HEADERS => {} ,
161
180
  FAIL => [] ,
162
181
  LOG => [] ,
163
- ASYNC => if block_given?
164
- lambda{ |res| yield(res) }
165
- else
166
- nil
167
- end}.merge(env)))
182
+ ASYNC => !!k }.merge(env)),
183
+ &(k || Middleware.id))
184
+
185
+ # under ASYNC callback, response might not be a response hash
186
+ # in that case (maybe in a user created engine), Client#wait
187
+ # won't work because we have no way to track the future.
188
+ if response.kind_of?(Hash) && RestCore.const_defined?(:Future) &&
189
+ response[FUTURE].kind_of?(Future)
190
+ mutex.synchronize{ futures << WeakRef.new(response[FUTURE]) }
191
+ end
168
192
 
169
193
  if block_given?
170
194
  self
@@ -174,26 +198,15 @@ module RestCore::Client
174
198
  end
175
199
 
176
200
  def build_env env={}
177
- string_keys(attributes).merge(string_keys(env))
201
+ Middleware.string_keys(attributes).merge(Middleware.string_keys(env))
178
202
  end
179
203
  # ------------------------ instance ---------------------
180
204
 
181
205
 
182
206
 
183
207
  private
184
- def string_keys hash
185
- hash.inject({}){ |r, (k, v)|
186
- if v.kind_of?(Hash)
187
- r[k.to_s] = case k.to_s
188
- when REQUEST_QUERY, REQUEST_PAYLOAD, REQUEST_HEADERS
189
- string_keys(v)
190
- else; v
191
- end
192
- else
193
- r[k.to_s] = v
194
- end
195
- r
196
- }
208
+ def mutex
209
+ @mutex ||= Mutex.new
197
210
  end
198
211
 
199
212
  def lighten_hash hash