rest-core 3.6.0 → 4.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 (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