rest-core 1.0.3 → 2.0.0

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