rest-core 2.1.2 → 3.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/.travis.yml +3 -5
  4. data/CHANGES.md +65 -5
  5. data/Gemfile +10 -5
  6. data/NOTE.md +1 -1
  7. data/README.md +194 -128
  8. data/Rakefile +8 -34
  9. data/TODO.md +3 -2
  10. data/example/simple.rb +6 -4
  11. data/example/use-cases.rb +39 -122
  12. data/lib/rest-core.rb +14 -5
  13. data/lib/rest-core/builder.rb +12 -2
  14. data/lib/rest-core/client.rb +31 -25
  15. data/lib/rest-core/engine.rb +39 -0
  16. data/lib/rest-core/engine/http-client.rb +41 -0
  17. data/lib/rest-core/engine/net-http-persistent.rb +21 -0
  18. data/lib/rest-core/engine/rest-client.rb +13 -42
  19. data/lib/rest-core/event_source.rb +91 -0
  20. data/lib/rest-core/middleware.rb +17 -11
  21. data/lib/rest-core/middleware/error_detector.rb +1 -6
  22. data/lib/rest-core/middleware/oauth1_header.rb +1 -0
  23. data/lib/rest-core/middleware/oauth2_header.rb +20 -8
  24. data/lib/rest-core/middleware/oauth2_query.rb +1 -0
  25. data/lib/rest-core/middleware/timeout.rb +5 -19
  26. data/lib/rest-core/promise.rb +137 -0
  27. data/lib/rest-core/test.rb +2 -43
  28. data/lib/rest-core/thread_pool.rb +122 -0
  29. data/lib/rest-core/timer.rb +30 -0
  30. data/lib/rest-core/util/hmac.rb +0 -8
  31. data/lib/rest-core/version.rb +1 -1
  32. data/lib/rest-core/wrapper.rb +1 -1
  33. data/rest-core.gemspec +36 -25
  34. data/task/README.md +54 -0
  35. data/task/gemgem.rb +150 -156
  36. data/test/test_builder.rb +2 -2
  37. data/test/test_cache.rb +8 -8
  38. data/test/test_client.rb +16 -6
  39. data/test/test_client_oauth1.rb +1 -1
  40. data/test/test_event_source.rb +77 -0
  41. data/test/test_follow_redirect.rb +1 -1
  42. data/test/test_future.rb +16 -0
  43. data/test/test_oauth2_header.rb +28 -0
  44. data/test/test_promise.rb +89 -0
  45. data/test/test_rest-client.rb +21 -0
  46. data/test/test_thread_pool.rb +10 -0
  47. data/test/test_timeout.rb +13 -8
  48. metadata +61 -37
  49. data/example/multi.rb +0 -44
  50. data/lib/rest-core/engine/auto.rb +0 -25
  51. data/lib/rest-core/engine/em-http-request.rb +0 -90
  52. data/lib/rest-core/engine/future/future.rb +0 -107
  53. data/lib/rest-core/engine/future/future_fiber.rb +0 -32
  54. data/lib/rest-core/engine/future/future_thread.rb +0 -29
  55. data/lib/rest-core/middleware/timeout/timer_em.rb +0 -26
  56. data/lib/rest-core/middleware/timeout/timer_thread.rb +0 -36
  57. data/task/.gitignore +0 -1
  58. data/test/test_em-http-request.rb +0 -186
@@ -1,44 +0,0 @@
1
-
2
- require 'fiber'
3
- require 'em-http-request'
4
- require 'rest-core'
5
-
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
11
- end
12
-
13
- client = YourClient.new
14
- puts "rest-client with threads doing concurrent requests"
15
- a = [client.get('cardinalblue'), client.get('godfat')]
16
- puts "It's not blocking... but doing concurrent requests underneath"
17
- p a.map{ |r| r['name'] } # 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
- a = [client.get('cardinalblue'), client.get('godfat')]
26
- p a.map{ |r| r['name'] } # here we want the values, so it blocks here
27
- puts "DONE"
28
- EM.stop
29
- }
30
- puts "It's not blocking... but doing concurrent requests underneath"
31
- }
32
-
33
- puts; puts
34
-
35
- puts "eventmachine with fibers doing concurrent requests"
36
- EM.run{
37
- Fiber.new{
38
- a = [client.get('cardinalblue'), client.get('godfat')]
39
- p a.map{ |r| r['name'] } # here we want the values, so it blocks here
40
- puts "DONE"
41
- EM.stop
42
- }.resume
43
- puts "It's not blocking... but doing concurrent requests underneath"
44
- }
@@ -1,25 +0,0 @@
1
-
2
- require 'rest-core/middleware'
3
-
4
- class RestCore::Auto
5
- include RestCore::Middleware
6
- def call env, &k
7
- client = http_client
8
- client.call(log(env, "Auto picked: #{client.class}"), &k)
9
- end
10
-
11
- def http_client
12
- if Object.const_defined?(:EventMachine) &&
13
- ::EventMachine.const_defined?(:HttpRequest) &&
14
- ::EventMachine.reactor_running? &&
15
- # it should be either wrapped around a thread or a fiber
16
- ((Thread.main != Thread.current) ||
17
- (Fiber.respond_to?(:current) && RootFiber != Fiber.current))
18
-
19
- @emhttprequest ||= RestCore::EmHttpRequest.new
20
-
21
- else
22
- @restclient ||= RestCore::RestClient.new
23
- end
24
- end
25
- end
@@ -1,90 +0,0 @@
1
-
2
- require 'em-http-request'
3
- require 'restclient/payload'
4
-
5
- require 'rest-core/engine/future/future'
6
- require 'rest-core/middleware'
7
-
8
- class RestCore::EmHttpRequest
9
- include RestCore::Middleware
10
- def call env, &k
11
- future = Future.create(env, k, env[ASYNC])
12
-
13
- # eventmachine is not thread-safe, so...
14
- # https://github.com/igrigorik/em-http-request/issues/190#issuecomment-16995528
15
- ::EventMachine.schedule{ request(future, env) }
16
-
17
- env.merge(RESPONSE_BODY => future.proxy_body,
18
- RESPONSE_STATUS => future.proxy_status,
19
- RESPONSE_HEADERS => future.proxy_headers,
20
- FUTURE => future)
21
- end
22
-
23
- def request future, env
24
- payload, headers = Payload.generate_with_headers(env[REQUEST_PAYLOAD],
25
- env[REQUEST_HEADERS])
26
- args = if payload.nil?
27
- {}
28
- else
29
- tmpfile = payload2file(payload)
30
- if tmpfile.respond_to?(:path)
31
- {:file => tmpfile.path}
32
- else
33
- {:body => tmpfile}
34
- end
35
- end.merge(:head => headers)
36
-
37
- client = ::EventMachine::HttpRequest.new(request_uri(env)).
38
- send(env[REQUEST_METHOD], args)
39
-
40
- client.callback{
41
- close_tmpfile(tmpfile)
42
- future.on_load(client.response,
43
- client.response_header.status,
44
- client.response_header)}
45
-
46
- client.errback{
47
- close_client(client)
48
- close_tmpfile(tmpfile)
49
- future.on_error(client.error)}
50
-
51
- env[TIMER].on_timeout{
52
- close_client(client)
53
- close_tmpfile(tmpfile)
54
- future.on_error(env[TIMER].error)
55
- } if env[TIMER]
56
- end
57
-
58
- def payload2file payload
59
- if payload.io.respond_to?(:path) # already a file
60
- payload.io
61
-
62
- elsif payload.size == 0 || # probably a socket, buffer to disc
63
- payload.size >= 81920 # probably too large, buffer to disc
64
- create_tmpfile(payload.io)
65
-
66
- else # probably not worth buffering to disc
67
- payload.read
68
- end
69
- end
70
-
71
- def create_tmpfile io
72
- tempfile = Tempfile.new("rest-core.em-http-request.#{rand(1_000_000)}")
73
- IO.copy_stream(io, tempfile)
74
- tempfile
75
- end
76
-
77
- def close_client client
78
- (client.instance_variable_get(:@callbacks)||[]).clear
79
- (client.instance_variable_get(:@errbacks )||[]).clear
80
- client.close
81
- end
82
-
83
- def close_tmpfile tmpfile
84
- if tmpfile.respond_to?(:close!) # tempfile
85
- tmpfile.close!
86
- elsif tmpfile.respond_to?(:close) # regular IO
87
- tmpfile.close
88
- end
89
- end
90
- end
@@ -1,107 +0,0 @@
1
-
2
- require 'rest-core'
3
-
4
- class RestCore::Future
5
- include RestCore
6
-
7
- class Proxy < BasicObject
8
- def initialize future, target
9
- @future, @target = future, target
10
- end
11
-
12
- def method_missing msg, *args, &block
13
- @future.yield[@target].__send__(msg, *args, &block)
14
- end
15
- end
16
-
17
- def self.create *args, &block
18
- if Fiber.respond_to?(:current) && RootFiber != Fiber.current &&
19
- # because under a thread, Fiber.current won't return the root fiber
20
- Thread.main == Thread.current
21
- FutureFiber .new(*args, &block)
22
- else
23
- FutureThread.new(*args, &block)
24
- end
25
- end
26
-
27
- def initialize env, k, immediate
28
- self.env = env
29
- self.k = k
30
- self.immediate = immediate
31
- self.response, self.body, self.status, self.headers, self.error = nil
32
- end
33
-
34
- def proxy_body ; Proxy.new(self, RESPONSE_BODY ); end
35
- def proxy_status ; Proxy.new(self, RESPONSE_STATUS ); end
36
- def proxy_headers; Proxy.new(self, RESPONSE_HEADERS); end
37
-
38
- def wrap ; raise NotImplementedError; end
39
- def wait ; raise NotImplementedError; end
40
- def resume; raise NotImplementedError; end
41
-
42
- def loaded?
43
- !!status
44
- end
45
-
46
- def yield
47
- wait
48
- callback
49
- end
50
-
51
- def callback
52
- self.response ||= k.call(
53
- env.merge(RESPONSE_BODY => body ,
54
- RESPONSE_STATUS => status,
55
- RESPONSE_HEADERS => headers,
56
- FAIL => ((env[FAIL]||[]) + [error]).compact,
57
- LOG => (env[LOG] ||[]) +
58
- ["Future picked: #{self.class}"]))
59
- end
60
-
61
- def callback_in_async
62
- callback
63
- rescue Exception => e
64
- # nothing we can do here for an asynchronous exception,
65
- # so we just log the error
66
- logger = method(:warn) # TODO: add error_log_method
67
- logger.call "RestCore: ERROR: #{e}\n from #{e.backtrace.inspect}"
68
- end
69
-
70
- def on_load body, status, headers
71
- env[TIMER].cancel if env[TIMER]
72
- synchronize{
73
- self.body, self.status, self.headers = body, status, headers
74
- }
75
- # under ASYNC callback, should call immediately
76
- next_tick{ callback_in_async } if immediate
77
- resume # client or response might be waiting
78
- end
79
-
80
- def on_error error
81
- self.error = if error.kind_of?(Exception)
82
- error
83
- else
84
- Error.new(error || 'unknown')
85
- end
86
- on_load('', 0, {})
87
- end
88
-
89
- protected
90
- attr_accessor :env, :k, :immediate,
91
- :response, :body, :status, :headers, :error
92
-
93
- private
94
- def synchronize; yield; end
95
- # next_tick is used for telling the reactor that there's something else
96
- # should be done, don't sleep and don't stop at the moment
97
- def next_tick
98
- if Object.const_defined?(:EventMachine) && EventMachine.reactor_running?
99
- EventMachine.next_tick{ yield }
100
- else
101
- yield
102
- end
103
- end
104
-
105
- autoload :FutureFiber , 'rest-core/engine/future/future_fiber'
106
- autoload :FutureThread, 'rest-core/engine/future/future_thread'
107
- end
@@ -1,32 +0,0 @@
1
-
2
- require 'fiber'
3
-
4
- class RestCore::Future::FutureFiber < RestCore::Future
5
- def initialize *args
6
- super
7
- self.fibers = []
8
- end
9
-
10
- def wrap
11
- Fiber.new{ yield }.resume
12
- end
13
-
14
- def wait
15
- fibers << Fiber.current
16
- Fiber.yield until loaded? # it might be resumed by some other futures!
17
- end
18
-
19
- def resume
20
- return if fibers.empty?
21
- current_fibers = fibers.dup
22
- fibers.clear
23
- current_fibers.each{ |f|
24
- next unless f.alive?
25
- next_tick{ f.resume }
26
- }
27
- resume
28
- end
29
-
30
- protected
31
- attr_accessor :fibers
32
- end
@@ -1,29 +0,0 @@
1
-
2
- require 'thread'
3
-
4
- class RestCore::Future::FutureThread < RestCore::Future
5
- def initialize *args
6
- super
7
- self.condv = ConditionVariable.new
8
- self.mutex = Mutex.new
9
- end
10
-
11
- def wrap
12
- Thread.new{ yield }
13
- end
14
-
15
- def wait
16
- # it might be awaken by some other futures!
17
- synchronize{ condv.wait(mutex) until loaded? } unless loaded?
18
- end
19
-
20
- def resume
21
- condv.broadcast
22
- end
23
-
24
- protected
25
- attr_accessor :condv, :mutex
26
-
27
- private
28
- def synchronize; mutex.synchronize{ yield }; end
29
- end
@@ -1,26 +0,0 @@
1
-
2
- require 'eventmachine'
3
-
4
- class RestCore::Timeout::TimerEm < ::EventMachine::Timer
5
- attr_accessor :timeout, :error
6
-
7
- def initialize timeout, error, &block
8
- super(timeout, &block) if block_given?
9
- self.timeout = timeout
10
- self.error = error
11
- @canceled = false
12
- end
13
-
14
- def on_timeout &block
15
- send(:initialize, timeout, error, &block)
16
- end
17
-
18
- def cancel
19
- super
20
- @canceled = true
21
- end
22
-
23
- def canceled?
24
- @canceled
25
- end
26
- end
@@ -1,36 +0,0 @@
1
-
2
- class RestCore::Timeout::TimerThread
3
- attr_accessor :timeout, :error
4
-
5
- def initialize timeout, error, &block
6
- t = Thread.current
7
- self.timeout = timeout
8
- self.error = error
9
- self.block = block || lambda{ t.raise error }
10
- @canceled = false
11
- start
12
- end
13
-
14
- def on_timeout &block
15
- self.block = block
16
- end
17
-
18
- def cancel
19
- @canceled = true
20
- end
21
-
22
- def canceled?
23
- @canceled
24
- end
25
-
26
- def start
27
- return if timeout.nil? || timeout.zero?
28
- self.thread = Thread.new{
29
- sleep(timeout)
30
- block.call unless canceled?
31
- }
32
- end
33
-
34
- protected
35
- attr_accessor :block, :thread
36
- end
@@ -1 +0,0 @@
1
- *.rbc
@@ -1,186 +0,0 @@
1
-
2
- require 'rest-core/test'
3
-
4
- describe RC::EmHttpRequest do
5
- should 'raise RC::Error' do
6
- EM.run{Fiber.new{
7
- lambda{
8
- RC::Universal.new.get('http://localhost:1').tap{}
9
- }.should.raise(RC::Error)
10
- EM.stop
11
- }.resume}
12
- end
13
-
14
- should 'never crash EM!' do
15
- EM.error_handler{ |e| e.should.kind_of?(NoMethodError); EM.stop}
16
- EM.run{Fiber.new{
17
- RC::Simple.new.get('http://localhost:1').no_such_method
18
- }.resume}
19
- end
20
-
21
- describe 'POST Payload' do
22
- after do
23
- WebMock.reset!
24
- end
25
-
26
- client = RC::Builder.client
27
- client.builder.run(RC::EmHttpRequest)
28
- path = 'http://example.com'
29
- ok = 'OK'
30
- c = client.new
31
-
32
- post = lambda do |payload, body|
33
- stub_request(:post, path).with(:body => body).to_return(:body => ok)
34
- EM.error_handler{ |e| e.should.kind_of?(NilClass); EM.stop }
35
- EM.run{ EM.defer{
36
- c.post(path, payload).should.eq ok
37
- EM.next_tick{ EM.stop }
38
- }}
39
- end
40
-
41
- should 'post with string' do
42
- post['string', 'string']
43
- end
44
-
45
- should 'post with file' do
46
- File.open(__FILE__) do |f|
47
- b = f.read
48
- f.rewind
49
- post[f, b]
50
- end
51
- end
52
-
53
- should 'post with socket' do
54
- rd, wr = IO.pipe
55
- wr.write('socket')
56
- wr.close
57
- post[rd, 'socket']
58
- end
59
- end
60
-
61
- # ----------------------------------------------------------------------
62
-
63
- describe RC::Simple do
64
- before do
65
- @path = 'http://example.com'
66
- stub_request(:get, @path).to_return(:body => 'OK')
67
- end
68
-
69
- should 'work with EM' do
70
- EM.run{Fiber.new{
71
- RC::Simple.new.get(@path).should.eq 'OK'; EM.stop}.resume}
72
- end
73
- end
74
-
75
- # ----------------------------------------------------------------------
76
-
77
- describe RC::Timeout do
78
- after do
79
- WebMock.reset!
80
- RR.verify
81
- end
82
-
83
- should 'cancel timeout for fiber' do
84
- any_instance_of(RC::Timeout::TimerEm) do |timer|
85
- proxy.mock(timer).cancel.times(2)
86
- end
87
- path = 'http://example.com/'
88
- stub_request(:get, path).to_return(:body => 'response')
89
- c = RC::Builder.client do
90
- use RC::Timeout, 10
91
- use RC::Cache, {}, 3600
92
- run RC::EmHttpRequest
93
- end.new
94
- EM.run{ Fiber.new{
95
- c.request(RC::REQUEST_PATH => path).should.eq 'response'
96
- c.request(RC::REQUEST_PATH => path).should.eq 'response'
97
- EM.stop }.resume }
98
- c.cache.size.should.eq 1
99
- end
100
-
101
- should 'cancel timeout for async' do
102
- path = 'http://example.com/'
103
- any_instance_of(RC::Timeout::TimerEm) do |timer|
104
- mock(timer).cancel.times(2)
105
- end
106
- stub_request(:get, path).to_return(:body => 'response')
107
- c = RC::Builder.client do
108
- use RC::Timeout, 10
109
- use RC::Cache, {}, 3600
110
- run RC::EmHttpRequest
111
- end.new
112
- EM.run{
113
- c.request_full(RC::REQUEST_PATH => path){
114
- c.request_full(RC::REQUEST_PATH => path){
115
- EM.stop }}}
116
- c.cache.size.should.eq 1
117
- end
118
-
119
- should 'return correct result for futures' do
120
- path = 'http://example.com/'
121
- stub_request(:get, path).to_return(:body => 'response')
122
-
123
- c = RC::Builder.client do
124
- use RC::Timeout, 10
125
- run RC::EmHttpRequest
126
- end.new
127
- EM.run{Fiber.new{c.get(path).should.eq('response');EM.stop}.resume}
128
- end
129
-
130
- describe 'raise exception' do
131
- should 'default timeout' do
132
- c = RC::Builder.client do
133
- use RC::Timeout, 0.00001
134
- run Class.new{
135
- def call env
136
- sleep 1
137
- yield(env)
138
- end
139
- }
140
- end.new
141
- lambda{ c.get('') }.should.raise ::Timeout::Error
142
- end
143
-
144
- should 'future timeout' do
145
- port = 35795
146
- path = "http://localhost:#{port}/"
147
-
148
- c = RC::Builder.client do
149
- use RC::Timeout, 0.00001
150
- run RC::EmHttpRequest
151
- end.new
152
-
153
- EM.run{
154
- EM.start_server '127.0.0.1', port, Module.new{
155
- def receive_data data; end
156
- }
157
- Fiber.new{
158
- begin
159
- c.get(path).tap{}
160
- rescue => e
161
- e.should.kind_of ::Timeout::Error
162
- EM.stop
163
- end
164
- }.resume}
165
- end
166
-
167
- should 'async timeout' do
168
- port = 35795
169
- path = "http://localhost:#{port}/"
170
-
171
- c = RC::Builder.client do
172
- use RC::Timeout, 0.00001
173
- use RC::ErrorHandler
174
- run RC::EmHttpRequest
175
- end.new
176
-
177
- EM.run{
178
- EM.start_server '127.0.0.1', port, Module.new{
179
- def receive_data data; end
180
- }
181
- c.get(path){ |e| e.should.kind_of ::Timeout::Error; EM.stop }
182
- }
183
- end
184
- end
185
- end
186
- end unless RUBY_ENGINE == 'jruby'