rest-core 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/.travis.yml +0 -1
  4. data/CHANGES.md +28 -0
  5. data/Gemfile +0 -2
  6. data/README.md +14 -95
  7. data/Rakefile +16 -3
  8. data/example/simple.rb +1 -0
  9. data/example/use-cases.rb +15 -3
  10. data/lib/rest-core.rb +38 -75
  11. data/lib/rest-core/client/universal.rb +3 -1
  12. data/lib/rest-core/client_oauth1.rb +64 -59
  13. data/lib/rest-core/event.rb +9 -11
  14. data/lib/rest-core/middleware/auth_basic.rb +21 -21
  15. data/lib/rest-core/middleware/bypass.rb +8 -8
  16. data/lib/rest-core/middleware/cache.rb +94 -90
  17. data/lib/rest-core/middleware/clash_response.rb +15 -14
  18. data/lib/rest-core/middleware/common_logger.rb +27 -26
  19. data/lib/rest-core/middleware/default_headers.rb +8 -8
  20. data/lib/rest-core/middleware/default_payload.rb +8 -8
  21. data/lib/rest-core/middleware/default_query.rb +8 -8
  22. data/lib/rest-core/middleware/default_site.rb +12 -12
  23. data/lib/rest-core/middleware/defaults.rb +38 -38
  24. data/lib/rest-core/middleware/error_detector.rb +10 -10
  25. data/lib/rest-core/middleware/error_detector_http.rb +6 -4
  26. data/lib/rest-core/middleware/error_handler.rb +14 -14
  27. data/lib/rest-core/middleware/follow_redirect.rb +28 -27
  28. data/lib/rest-core/middleware/json_request.rb +13 -11
  29. data/lib/rest-core/middleware/json_response.rb +29 -28
  30. data/lib/rest-core/middleware/oauth1_header.rb +84 -83
  31. data/lib/rest-core/middleware/oauth2_header.rb +27 -25
  32. data/lib/rest-core/middleware/oauth2_query.rb +15 -15
  33. data/lib/rest-core/middleware/query_response.rb +14 -14
  34. data/lib/rest-core/middleware/retry.rb +25 -23
  35. data/lib/rest-core/middleware/smash_response.rb +15 -14
  36. data/lib/rest-core/middleware/timeout.rb +18 -19
  37. data/lib/rest-core/test.rb +1 -18
  38. data/lib/rest-core/util/clash.rb +38 -37
  39. data/lib/rest-core/util/config.rb +40 -39
  40. data/lib/rest-core/util/dalli_extension.rb +11 -10
  41. data/lib/rest-core/util/hmac.rb +9 -8
  42. data/lib/rest-core/util/json.rb +55 -54
  43. data/lib/rest-core/util/parse_link.rb +13 -12
  44. data/lib/rest-core/util/parse_query.rb +24 -22
  45. data/lib/rest-core/version.rb +1 -1
  46. data/rest-core.gemspec +121 -158
  47. data/test/test_cache.rb +2 -0
  48. data/test/test_default_payload.rb +1 -1
  49. data/test/test_error_handler.rb +5 -4
  50. data/test/test_timeout.rb +9 -8
  51. data/test/test_universal.rb +8 -0
  52. metadata +9 -73
  53. data/lib/rest-core/builder.rb +0 -162
  54. data/lib/rest-core/client.rb +0 -277
  55. data/lib/rest-core/client/simple.rb +0 -2
  56. data/lib/rest-core/engine.rb +0 -36
  57. data/lib/rest-core/engine/dry.rb +0 -9
  58. data/lib/rest-core/engine/http-client.rb +0 -41
  59. data/lib/rest-core/error.rb +0 -5
  60. data/lib/rest-core/event_source.rb +0 -137
  61. data/lib/rest-core/middleware.rb +0 -151
  62. data/lib/rest-core/promise.rb +0 -249
  63. data/lib/rest-core/thread_pool.rb +0 -131
  64. data/lib/rest-core/timer.rb +0 -58
  65. data/lib/rest-core/util/payload.rb +0 -173
  66. data/test/test_builder.rb +0 -40
  67. data/test/test_client.rb +0 -177
  68. data/test/test_event_source.rb +0 -159
  69. data/test/test_future.rb +0 -16
  70. data/test/test_httpclient.rb +0 -118
  71. data/test/test_payload.rb +0 -204
  72. data/test/test_promise.rb +0 -146
  73. data/test/test_simple.rb +0 -38
  74. data/test/test_thread_pool.rb +0 -10
@@ -1,2 +0,0 @@
1
-
2
- RestCore::Simple = RestCore::Builder.client
@@ -1,36 +0,0 @@
1
-
2
- require 'rest-core/promise'
3
- require 'rest-core/middleware'
4
-
5
- class RestCore::Engine
6
- def self.members; [:config_engine]; end
7
- include RestCore::Middleware
8
-
9
- def call env, &k
10
- req = env.merge(REQUEST_URI => request_uri(env))
11
- promise = Promise.new(req, k, req[ASYNC])
12
- promise.defer{ request(promise, req) }
13
- promise.future_response
14
- end
15
-
16
- private
17
- def payload_and_headers env
18
- if has_payload?(env)
19
- Payload.generate_with_headers(env[REQUEST_PAYLOAD],
20
- env[REQUEST_HEADERS])
21
- else
22
- [{}, env[REQUEST_HEADERS]]
23
- end
24
- end
25
-
26
- def normalize_headers headers
27
- headers.inject({}){ |r, (k, v)|
28
- r[k.to_s.upcase.tr('-', '_')] = if v.kind_of?(Array) && v.size == 1
29
- v.first
30
- else
31
- v
32
- end
33
- r
34
- }
35
- end
36
- end
@@ -1,9 +0,0 @@
1
-
2
- require 'rest-core/middleware'
3
-
4
- class RestCore::Dry
5
- include RestCore::Middleware
6
- def call env
7
- yield(env)
8
- end
9
- end
@@ -1,41 +0,0 @@
1
-
2
- require 'httpclient'
3
- # httpclient would require something (cookie manager) while initialized,
4
- # so we should try to force requiring them to avoid require deadlock!
5
- HTTPClient.new
6
-
7
- require 'rest-core/engine'
8
-
9
- class RestCore::HttpClient < RestCore::Engine
10
- private
11
- def request promise, env
12
- client = ::HTTPClient.new
13
- client.cookie_manager = nil
14
- client.follow_redirect_count = 0
15
- client.transparent_gzip_decompression = true
16
- config = config_engine(env) and config.call(client)
17
- payload, headers = payload_and_headers(env)
18
-
19
- if env[HIJACK]
20
- request_async(client, payload, headers, promise, env)
21
- else
22
- request_sync(client, payload, headers, promise, env)
23
- end
24
- end
25
-
26
- def request_sync client, payload, headers, promise, env
27
- res = client.request(env[REQUEST_METHOD], env[REQUEST_URI], nil,
28
- payload, {'User-Agent' => 'Ruby'}.merge(headers))
29
-
30
- promise.fulfill(res.content, res.status,
31
- normalize_headers(res.header.all))
32
- end
33
-
34
- def request_async client, payload, headers, promise, env
35
- res = client.request_async(env[REQUEST_METHOD], env[REQUEST_URI], nil,
36
- payload, {'User-Agent' => 'Ruby'}.merge(headers)).pop
37
-
38
- promise.fulfill('', res.status,
39
- normalize_headers(res.header.all), res.content)
40
- end
41
- end
@@ -1,5 +0,0 @@
1
-
2
- module RestCore
3
- class Error < RuntimeError
4
- end
5
- end
@@ -1,137 +0,0 @@
1
-
2
- require 'thread'
3
- require 'rest-core'
4
-
5
- class RestCore::EventSource < Struct.new(:client, :path, :query, :opts,
6
- :socket)
7
- include RestCore
8
- READ_WAIT = 35
9
-
10
- def start
11
- self.mutex = Mutex.new
12
- self.condv = ConditionVariable.new
13
- @onopen ||= nil
14
- @onmessage ||= nil
15
- @onerror ||= nil
16
- @onreconnect ||= nil
17
- @closed ||= false
18
- reconnect
19
- self
20
- end
21
-
22
- def closed?
23
- !!(socket && socket.closed?) || @closed
24
- end
25
-
26
- def close
27
- socket && socket.close
28
- rescue IOError
29
- end
30
-
31
- def wait
32
- raise RC::Error.new("Not yet started for: #{self}") unless mutex
33
- mutex.synchronize{ condv.wait(mutex) until closed? } unless closed?
34
- self
35
- end
36
-
37
- def onopen sock=nil, &cb
38
- if block_given?
39
- @onopen = cb
40
- else
41
- self.socket = sock # for you to track the socket
42
- @onopen.call(sock) if @onopen
43
- onmessage_for(sock)
44
- end
45
- self
46
- rescue Exception => e
47
- begin # close the socket since we're going to stop anyway
48
- sock.close # if we don't close it, client might wait forever
49
- rescue IOError
50
- end
51
- # let the client has a chance to handle this, and make signal
52
- onerror(e, sock)
53
- end
54
-
55
- def onmessage event=nil, data=nil, sock=nil, &cb
56
- if block_given?
57
- @onmessage = cb
58
- elsif @onmessage
59
- @onmessage.call(event, data, sock)
60
- end
61
- self
62
- end
63
-
64
- # would also be called upon closing, would always be called at least once
65
- def onerror error=nil, sock=nil, &cb
66
- if block_given?
67
- @onerror = cb
68
- else
69
- begin
70
- @onerror.call(error, sock) if @onerror
71
- onreconnect(error, sock)
72
- rescue Exception
73
- mutex.synchronize do
74
- @closed = true
75
- condv.signal # so we can't be reconnecting, need to try to unblock
76
- end
77
- raise
78
- end
79
- end
80
- self
81
- end
82
-
83
- # would be called upon closing,
84
- # and would try to reconnect if a callback is set and return true
85
- def onreconnect error=nil, sock=nil, &cb
86
- if block_given?
87
- @onreconnect = cb
88
- elsif closed? && @onreconnect && @onreconnect.call(error, sock)
89
- reconnect
90
- else
91
- mutex.synchronize do
92
- @closed = true
93
- condv.signal # we could be closing, let's try to unblock it
94
- end
95
- end
96
- self
97
- end
98
-
99
- protected
100
- attr_accessor :mutex, :condv
101
-
102
- private
103
- # called in requesting thread after the request is done
104
- def onmessage_for sock
105
- while IO.select([sock], [], [], READ_WAIT)
106
- event = sock.readline("\n\n").split("\n").inject({}) do |r, i|
107
- k, v = i.split(': ', 2)
108
- r[k] = v
109
- r
110
- end
111
- onmessage(event['event'], event['data'], sock)
112
- end
113
- close_sock(sock)
114
- onerror(EOFError.new, sock)
115
- rescue IOError, SystemCallError => e
116
- close_sock(sock)
117
- onerror(e, sock)
118
- end
119
-
120
- def close_sock sock
121
- sock.close
122
- rescue IOError => e
123
- onerror(e, sock)
124
- end
125
-
126
- def reconnect
127
- o = {REQUEST_HEADERS => {'Accept' => 'text/event-stream'},
128
- HIJACK => true}.merge(opts)
129
- client.get(path, query, o) do |sock|
130
- if sock.nil? || sock.kind_of?(Exception)
131
- onerror(sock)
132
- else
133
- onopen(sock)
134
- end
135
- end
136
- end
137
- end
@@ -1,151 +0,0 @@
1
-
2
- require 'uri'
3
- require 'rest-core'
4
-
5
- module RestCore::Middleware
6
- include RestCore
7
- METHODS_WITH_PAYLOAD = [:post, :put, :patch]
8
-
9
- def self.included mod
10
- mod.send(:include, RestCore)
11
- mod.send(:attr_reader, :app)
12
- mem = if mod.respond_to?(:members) then mod.members else [] end
13
- src = mem.map{ |member| <<-RUBY }
14
- attr_writer :#{member}
15
- def #{member} env
16
- if env.key?('#{member}')
17
- env['#{member}']
18
- else
19
- @#{member}
20
- end
21
- end
22
- RUBY
23
- args = [:app] + mem
24
- para_list = args.map{ |a| "#{a}=nil"}.join(', ')
25
- args_list = args .join(', ')
26
- ivar_list = args.map{ |a| "@#{a}" }.join(', ')
27
- src << <<-RUBY
28
- def initialize #{para_list}
29
- #{ivar_list} = #{args_list}
30
- end
31
- RUBY
32
- accessor = Module.new
33
- accessor.module_eval(src.join("\n"), __FILE__, __LINE__)
34
- mod.const_set(:Accessor, accessor)
35
- mod.send(:include, accessor)
36
- end
37
-
38
- def call env, &k; app.call(env, &(k || id)); end
39
- def id ; RC.id ; end
40
- def fail env, obj
41
- if obj
42
- env.merge(FAIL => (env[FAIL] || []) + [obj])
43
- else
44
- env
45
- end
46
- end
47
- def log env, obj
48
- if obj
49
- env.merge(LOG => (env[LOG] || []) + [obj])
50
- else
51
- env
52
- end
53
- end
54
- def run a=app
55
- if a.respond_to?(:app) && a.app
56
- run(a.app)
57
- else
58
- a
59
- end
60
- end
61
- def error_callback res, err
62
- res[CLIENT].error_callback.call(err) if
63
- res[CLIENT] && res[CLIENT].error_callback
64
- end
65
- def give_promise res
66
- res[CLIENT].give_promise(res) if res[CLIENT]
67
- end
68
-
69
- module_function
70
- def request_uri env
71
- # compacting the hash
72
- if (query = (env[REQUEST_QUERY] || {}).select{ |k, v| v }).empty?
73
- env[REQUEST_PATH].to_s
74
- else
75
- q = if env[REQUEST_PATH] =~ /\?/ then '&' else '?' end
76
- "#{env[REQUEST_PATH]}#{q}#{percent_encode(query)}"
77
- end
78
- end
79
- public :request_uri
80
-
81
- def percent_encode query
82
- query.sort.map{ |(k, v)|
83
- if v.kind_of?(Array)
84
- v.map{ |vv| "#{escape(k.to_s)}=#{escape(vv.to_s)}" }.join('&')
85
- else
86
- "#{escape(k.to_s)}=#{escape(v.to_s)}"
87
- end
88
- }.join('&')
89
- end
90
- public :percent_encode
91
-
92
- UNRESERVED = /[^a-zA-Z0-9\-\.\_\~]+/
93
- def escape string
94
- string.gsub(UNRESERVED) do |s|
95
- "%#{s.unpack('H2' * s.bytesize).join('%')}".upcase
96
- end
97
- end
98
- public :escape
99
-
100
- def has_payload? env
101
- METHODS_WITH_PAYLOAD.include?(env[REQUEST_METHOD])
102
- end
103
- public :has_payload?
104
-
105
- def contain_binary? payload
106
- return false unless payload
107
- return true if payload.respond_to?(:read)
108
- return true if payload.find{ |k, v|
109
- # if payload is an array, then v would be nil
110
- (v || k).respond_to?(:read) ||
111
- # if v is an array, it could contain binary data
112
- (v.kind_of?(Array) && v.any?{ |vv| vv.respond_to?(:read) }) }
113
- return false
114
- end
115
- public :contain_binary?
116
-
117
- def string_keys hash
118
- hash.inject({}){ |r, (k, v)|
119
- if v.kind_of?(Hash)
120
- r[k.to_s] = case k.to_s
121
- when REQUEST_QUERY, REQUEST_PAYLOAD, REQUEST_HEADERS
122
- string_keys(v)
123
- else; v
124
- end
125
- else
126
- r[k.to_s] = v
127
- end
128
- r
129
- }
130
- end
131
- public :string_keys
132
-
133
- # this method is intended to merge payloads if they are non-empty hashes,
134
- # but prefer the right most one if they are not hashes.
135
- def merge_hash *hashes
136
- hashes.reverse_each.inject do |r, i|
137
- if r.kind_of?(Hash)
138
- if i.kind_of?(Hash)
139
- Middleware.string_keys(i).merge(Middleware.string_keys(r))
140
- elsif r.empty?
141
- i # prefer non-empty ones
142
- else
143
- r # don't try to merge non-hashes
144
- end
145
- else
146
- r
147
- end
148
- end
149
- end
150
- public :merge_hash
151
- end
@@ -1,249 +0,0 @@
1
-
2
- require 'thread'
3
- require 'rest-core'
4
-
5
- class RestCore::Promise
6
- include RestCore
7
-
8
- class Future < BasicObject
9
- def initialize promise, target
10
- @promise, @target = promise, target
11
- end
12
-
13
- def method_missing msg, *args, &block
14
- @promise.yield[@target].__send__(msg, *args, &block)
15
- end
16
- end
17
-
18
- def self.claim env, k=RC.id, body, status, headers
19
- promise = new(env, k, env[ASYNC])
20
- promise.fulfill(body, status, headers)
21
- promise
22
- end
23
-
24
- def self.backtrace
25
- Thread.current[:backtrace] || []
26
- end
27
-
28
- # should never raise!
29
- def self.set_backtrace e
30
- e.set_backtrace((e.backtrace || caller) + backtrace)
31
- end
32
-
33
- def initialize env, k=RC.id, immediate=false, &job
34
- self.env = env
35
- self.k = [k]
36
- self.immediate = immediate
37
-
38
- self.body, self.status, self.headers, self.socket,
39
- self.response, self.error, self.called = nil
40
-
41
- self.condv = ConditionVariable.new
42
- self.mutex = Mutex.new
43
-
44
- defer(&job) if job
45
- end
46
-
47
- def inspect
48
- "<#{self.class.name} for #{env[REQUEST_URI]}>"
49
- end
50
-
51
- def future_body ; Future.new(self, RESPONSE_BODY ); end
52
- def future_status ; Future.new(self, RESPONSE_STATUS ); end
53
- def future_headers ; Future.new(self, RESPONSE_HEADERS); end
54
- def future_socket ; Future.new(self, RESPONSE_SOCKET ); end
55
- def future_failures; Future.new(self, FAIL) ; end
56
- def future_response
57
- env.merge(RESPONSE_BODY => future_body,
58
- RESPONSE_STATUS => future_status,
59
- RESPONSE_HEADERS => future_headers,
60
- RESPONSE_SOCKET => future_socket,
61
- FAIL => future_failures,
62
- PROMISE => self)
63
- end
64
-
65
- # called in client thread
66
- def defer
67
- if pool_size < 0 # negative number for blocking call
68
- self.thread = Thread.current # set working thread
69
- protected_yield{ yield } # avoid any exception and do the job
70
- else
71
- backtrace = caller + self.class.backtrace # retain the backtrace so far
72
- if pool_size > 0
73
- mutex.synchronize do
74
- # still timing it out if the task never processed
75
- env[TIMER].on_timeout{ cancel_task } if env[TIMER]
76
- self.task = client_class.thread_pool.defer(mutex) do
77
- Thread.current[:backtrace] = backtrace
78
- protected_yield{ yield }
79
- Thread.current[:backtrace] = nil
80
- end
81
- end
82
- else
83
- self.thread = Thread.new do
84
- Thread.current[:backtrace] = backtrace
85
- protected_yield{ yield }
86
- end
87
- end
88
- end
89
- end
90
-
91
- # called in client thread (client.wait)
92
- def wait
93
- # it might be awaken by some other futures!
94
- mutex.synchronize{ condv.wait(mutex) until done? } unless done?
95
- end
96
-
97
- # called in client thread (from the future (e.g. body))
98
- def yield
99
- wait
100
- callback
101
- end
102
-
103
- # called in requesting thread after the request is done
104
- def fulfill body, status, headers, socket=nil
105
- env[TIMER].cancel if env[TIMER]
106
- mutex.synchronize{ fulfilling(body, status, headers, socket) }
107
- end
108
-
109
- # called in requesting thread if something goes wrong or timed out
110
- def reject error
111
- env[TIMER].cancel if env[TIMER]
112
- mutex.synchronize{ rejecting(error) }
113
- end
114
-
115
- # append your actions, which would be called when we're calling back
116
- def then &action
117
- k << action
118
- self
119
- end
120
-
121
- # called in Client.defer to mark this promise as done
122
- def done body=''
123
- fulfill(body, 0, {})
124
- end
125
-
126
- # It's considered done only if the HTTP request is done, and we're not
127
- # in asynchronous mode otherwise the callback should be called first.
128
- # For synchronous mode, since we're waiting for the callback anyway,
129
- # we don't really have to check if it's called.
130
- def done?
131
- !!status && (!immediate || called)
132
- end
133
-
134
- protected
135
- attr_accessor :env, :k, :immediate,
136
- :body, :status, :headers, :socket,
137
- :response, :error, :called,
138
- :condv, :mutex, :task, :thread
139
-
140
- private
141
- def fulfilling body, status, headers, socket=nil
142
- self.body, self.status, self.headers, self.socket =
143
- body, status, headers, socket
144
- # under ASYNC callback, should call immediately
145
- callback if immediate
146
- ensure
147
- condv.broadcast # client or response might be waiting
148
- end
149
-
150
- def rejecting error
151
- self.error = if error.kind_of?(Exception)
152
- error
153
- else
154
- Error.new(error || 'unknown')
155
- end
156
- fulfilling('', 0, {})
157
- end
158
-
159
- # called in a new thread if pool_size == 0, otherwise from the pool
160
- # i.e. requesting thread
161
- def protected_yield
162
- if env[TIMER]
163
- timeout_protected_yield{ yield }
164
- else
165
- yield
166
- end
167
- rescue Exception => e
168
- mutex.synchronize do
169
- self.class.set_backtrace(e)
170
- if done? # log user callback error
171
- callback_error(e)
172
- else # IOError, SystemCallError, etc
173
- begin
174
- rejecting(e) # would call user callback
175
- rescue Exception => f # log user callback error
176
- callback_error(f){ self.class.set_backtrace(f) }
177
- end
178
- end
179
- end
180
- end
181
-
182
- def timeout_protected_yield
183
- # timeout might already be set for thread_pool (pool_size > 0)
184
- env[TIMER].on_timeout{ cancel_task } unless env[TIMER].timer
185
- yield
186
- ensure
187
- env[TIMER].cancel
188
- end
189
-
190
- # called in client thread, when yield is called
191
- def callback
192
- return response if called
193
- self.response = k.inject(
194
- env.merge(RESPONSE_BODY => body ,
195
- RESPONSE_STATUS => status,
196
- RESPONSE_HEADERS => headers,
197
- RESPONSE_SOCKET => socket,
198
- FAIL => ((env[FAIL]||[]) + [error]).compact,
199
- LOG => env[LOG] ||[])){ |r, i| i.call(r) }
200
- ensure
201
- self.called = true
202
- end
203
-
204
- # log user callback error
205
- def callback_error e
206
- never_raise_yield do
207
- yield if block_given?
208
- if env[CLIENT].error_callback
209
- env[CLIENT].error_callback.call(e)
210
- else
211
- warn "RestCore: ERROR: #{e}\n from #{e.backtrace.inspect}"
212
- end
213
- end
214
- end
215
-
216
- # timeout!
217
- def cancel_task backtrace=nil
218
- mutex.synchronize do
219
- next if done? # don't cancel if it's done
220
- if t = thread || task.thread
221
- t.raise(env[TIMER].error) # raise Timeout::Error to working thread
222
- else # task was queued and never started, just cancel it and
223
- begin # fulfill the promise with Timeout::Error
224
- task.cancel
225
- rejecting(env[TIMER].error)
226
- rescue Exception => e # log user callback error
227
- callback_error(e){e.set_backtrace(e.backtrace + (backtrace || []))}
228
- end
229
- end
230
- end
231
- end
232
-
233
- # only use this for unimportant stuffs and in most critical section
234
- # e.g. error logging in critical section
235
- def never_raise_yield
236
- yield
237
- rescue Exception => e
238
- Thread.main.raise(e) if !!$DEBUG
239
- end
240
-
241
- def client_class; env[CLIENT].class; end
242
- def pool_size
243
- @pool_size ||= if client_class.respond_to?(:pool_size)
244
- client_class.pool_size
245
- else
246
- 0
247
- end
248
- end
249
- end