rest-core 2.1.2 → 3.0.0

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