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,131 +0,0 @@
1
-
2
- # reference implementation: puma
3
- # https://github.com/puma/puma/blob/v2.7.1/lib/puma/thread_pool.rb
4
-
5
- require 'thread'
6
- require 'rest-core'
7
-
8
- class RestCore::ThreadPool
9
- include RestCore
10
-
11
- class Queue
12
- def initialize
13
- @queue = []
14
- @condv = ConditionVariable.new
15
- end
16
-
17
- def size
18
- @queue.size
19
- end
20
-
21
- def << task
22
- queue << task
23
- condv.signal
24
- end
25
-
26
- def pop mutex, timeout=60
27
- if queue.empty?
28
- condv.wait(mutex, timeout)
29
- queue.shift || lambda{ |_| false } # shutdown idle workers
30
- else
31
- queue.shift
32
- end
33
- end
34
-
35
- def clear
36
- queue.clear
37
- end
38
-
39
- protected
40
- attr_reader :queue, :condv
41
- end
42
-
43
- class Task < Struct.new(:job, :mutex, :thread, :cancelled)
44
- # this should never fail
45
- def call working_thread
46
- mutex.synchronize do
47
- return if cancelled
48
- self.thread = working_thread
49
- end
50
- job.call
51
- true
52
- end
53
-
54
- def cancel
55
- self.cancelled = true
56
- end
57
- end
58
-
59
- def self.[] client_class
60
- (@pools ||= {})[client_class] ||= new(client_class)
61
- end
62
-
63
- attr_reader :client_class, :workers
64
-
65
- def initialize client_class
66
- @client_class = client_class
67
- @queue = Queue.new
68
- @mutex = Mutex.new
69
- @workers = []
70
- @waiting = 0
71
- end
72
-
73
- def inspect
74
- "#<#{self.class.name} client_class=#{client_class}>"
75
- end
76
-
77
- def size
78
- workers.size
79
- end
80
-
81
- def max_size
82
- client_class.pool_size
83
- end
84
-
85
- def idle_time
86
- client_class.pool_idle_time
87
- end
88
-
89
- def defer promise_mutex, &job
90
- mutex.synchronize do
91
- task = Task.new(job, promise_mutex)
92
- queue << task
93
- spawn_worker if waiting < queue.size && workers.size < max_size
94
- task
95
- end
96
- end
97
-
98
- def trim force=false
99
- mutex.synchronize do
100
- queue << lambda{ |_| false } if force || waiting > 0
101
- end
102
- end
103
-
104
- # Block on shutting down, and should not add more jobs while shutting down
105
- def shutdown
106
- workers.size.times{ trim(true) }
107
- workers.first.join && trim(true) until workers.empty?
108
- mutex.synchronize{ queue.clear }
109
- end
110
-
111
- protected
112
- attr_reader :queue, :mutex, :condv, :waiting
113
-
114
- private
115
- def spawn_worker
116
- workers << Thread.new{
117
- Thread.current.abort_on_exception = true
118
-
119
- task = nil
120
- begin
121
- mutex.synchronize do
122
- @waiting += 1
123
- task = queue.pop(mutex, idle_time)
124
- @waiting -= 1
125
- end
126
- end while task.call(Thread.current)
127
-
128
- mutex.synchronize{ workers.delete(Thread.current) }
129
- }
130
- end
131
- end
@@ -1,58 +0,0 @@
1
-
2
- require 'thread'
3
- require 'timers'
4
-
5
- class RestCore::Timer
6
- @mutex = Mutex.new
7
- @interval = 1
8
-
9
- singleton_class.module_eval do
10
- attr_accessor :interval
11
-
12
- def group
13
- @group ||= @mutex.synchronize{ @group ||= group_new }
14
- end
15
-
16
- private
17
- def group_new
18
- g = Timers::Group.new
19
- g.every(interval){}
20
- @thread = Thread.new do
21
- begin
22
- g.wait
23
- rescue => e
24
- warn "RestCore::Timer: ERROR: #{e}\n from #{e.backtrace.inspect}"
25
- end while g.count > 1
26
- @group = nil
27
- end
28
- g
29
- end
30
- end
31
-
32
- attr_accessor :timeout, :error, :timer
33
- def initialize timeout, error, &block
34
- self.timeout = timeout
35
- self.error = error
36
- self.block = block
37
- start if block_given?
38
- end
39
-
40
- def on_timeout &block
41
- self.block = block
42
- start if block_given?
43
- end
44
-
45
- # should never raise!
46
- def cancel
47
- timer.cancel if timer
48
- self.block = nil
49
- end
50
-
51
- def start
52
- return if timeout.nil? || timeout.zero?
53
- self.timer = self.class.group.after(timeout){ block.call if block }
54
- end
55
-
56
- protected
57
- attr_accessor :block
58
- end
@@ -1,173 +0,0 @@
1
-
2
- # stolen and modified from rest-client
3
-
4
- require 'rest-core/error'
5
-
6
- begin
7
- require 'mime/types/columnar'
8
- rescue LoadError
9
- require 'mime/types'
10
- end
11
-
12
- require 'stringio'
13
- require 'tempfile'
14
-
15
- module RestCore; end
16
- class RestCore::Payload
17
- include RestCore
18
-
19
- def self.generate_with_headers payload, headers
20
- h = if p = generate(payload)
21
- p.headers.merge(headers)
22
- else
23
- headers
24
- end
25
- [p, h]
26
- end
27
-
28
- def self.generate payload
29
- if payload.respond_to?(:read)
30
- Streamed.new(payload)
31
-
32
- elsif payload.kind_of?(String)
33
- StreamedString.new(payload)
34
-
35
- elsif payload.kind_of?(Hash)
36
- if payload.empty?
37
- nil
38
-
39
- elsif Middleware.contain_binary?(payload)
40
- Multipart.new(payload)
41
-
42
- else
43
- UrlEncoded.new(payload)
44
- end
45
-
46
- else
47
- raise Error.new("Payload should be either String, Hash, or" \
48
- " responding to `read', but: #{payload.inspect}")
49
- end
50
- end
51
-
52
- # Payload API
53
- attr_reader :io
54
- alias_method :to_io, :io
55
-
56
- def initialize payload; @io = payload ; end
57
- def read bytes=nil; io.read(bytes) ; end
58
- def close ; io.close unless closed?; end
59
- def closed? ; io.closed? ; end
60
- def headers ; {} ; end
61
-
62
- def size
63
- if io.respond_to?(:size)
64
- io.size
65
- elsif io.respond_to?(:stat)
66
- io.stat.size
67
- else
68
- 0
69
- end
70
- end
71
-
72
- class Streamed < Payload
73
- def headers
74
- {'Content-Length' => size.to_s}
75
- end
76
- end
77
-
78
- class StreamedString < Streamed
79
- def initialize payload
80
- super(StringIO.new(payload))
81
- end
82
- end
83
-
84
- class UrlEncoded < StreamedString
85
- def initialize payload
86
- super(RestCore::Middleware.percent_encode(payload))
87
- end
88
-
89
- def headers
90
- super.merge('Content-Type' => 'application/x-www-form-urlencoded')
91
- end
92
- end
93
-
94
- class Multipart < Streamed
95
- EOL = "\r\n"
96
-
97
- def initialize payload
98
- super(Tempfile.new("rest-core.payload.#{boundary}"))
99
-
100
- io.binmode
101
-
102
- payload.each_with_index do |(k, v), i|
103
- if v.kind_of?(Array)
104
- v.each{ |vv| part(k, vv) }
105
- else
106
- part(k, v)
107
- end
108
- end
109
- io.write("--#{boundary}--#{EOL}")
110
- io.rewind
111
- end
112
-
113
- def part k, v
114
- io.write("--#{boundary}#{EOL}Content-Disposition: form-data")
115
- io.write("; name=\"#{k}\"") if k
116
- if v.respond_to?(:read)
117
- part_binary(k, v)
118
- else
119
- part_plantext(k, v)
120
- end
121
- end
122
-
123
- def part_plantext k, v
124
- io.write("#{EOL}#{EOL}#{v}#{EOL}")
125
- end
126
-
127
- def part_binary k, v
128
- if v.respond_to?(:original_filename) # Rails
129
- io.write("; filename=\"#{v.original_filename}\"#{EOL}")
130
- elsif v.respond_to?(:path) # files
131
- io.write("; filename=\"#{File.basename(v.path)}\"#{EOL}")
132
- else # io
133
- io.write("; filename=\"#{k}\"#{EOL}")
134
- end
135
-
136
- # supply your own content type for regular files, will you?
137
- if v.respond_to?(:content_type) # Rails
138
- io.write("Content-Type: #{v.content_type}#{EOL}#{EOL}")
139
- elsif v.respond_to?(:path) && type = mime_type(v.path) # files
140
- io.write("Content-Type: #{type}#{EOL}#{EOL}")
141
- else
142
- io.write(EOL)
143
- end
144
-
145
- while data = v.read(8192)
146
- io.write(data)
147
- end
148
-
149
- io.write(EOL)
150
-
151
- ensure
152
- v.close if v.respond_to?(:close)
153
- end
154
-
155
- def mime_type path
156
- mime = MIME::Types.type_for(path)
157
- mime.first && mime.first.content_type
158
- end
159
-
160
- def boundary
161
- @boundary ||= rand(1_000_000).to_s
162
- end
163
-
164
- def headers
165
- super.merge('Content-Type' =>
166
- "multipart/form-data; boundary=#{boundary}")
167
- end
168
-
169
- def close
170
- io.close! unless io.closed?
171
- end
172
- end
173
- end
@@ -1,40 +0,0 @@
1
-
2
- require 'rest-core/test'
3
-
4
- describe RC::Builder do
5
- would 'default client app is a kind of RestCore::Engine' do
6
- RC::Builder.client.new.app.should.kind_of? RC::Engine
7
- end
8
-
9
- would 'default app is a kind of RestCore::Engine' do
10
- RC::Builder.new.to_app.should.kind_of? RC::Engine
11
- end
12
-
13
- would 'switch default_engine to RestCore::Dry a' do
14
- builder = Class.new(RC::Builder)
15
- builder.default_engine = RC::Dry
16
- builder.new.to_app.class.should.eq RC::Dry
17
- end
18
-
19
- would 'switch default_engine to RestCore::Dry b' do
20
- builder = RC::Builder.dup
21
- builder.default_engine = RC::Dry
22
- builder.client.new.app.class.should.eq RC::Dry
23
- end
24
-
25
- would 'accept middleware without a member' do
26
- RC::Builder.client{
27
- use Class.new.send(:include, RC::Middleware)
28
- }.members.should.eq [:config_engine]
29
- end
30
-
31
- would 'not have duplicated fields' do
32
- middleware = Class.new do
33
- def self.members; [:value]; end
34
- include RC::Middleware
35
- end
36
- client = RC::Builder.client(:value){ use middleware }.new
37
- client.value = 10
38
- client.value.should.eq 10
39
- end
40
- end
@@ -1,177 +0,0 @@
1
-
2
- require 'rest-core/test'
3
-
4
- describe RC::Simple do
5
- after do
6
- WebMock.reset!
7
- Muack.verify
8
- end
9
-
10
- url = 'http://example.com/'
11
-
12
- would 'do simple request' do
13
- c = RC::Simple.new
14
- [:get, :post, :delete, :put, :patch].each do |method|
15
- stub_request(method, url).to_return(:body => '[]')
16
- c.send(method, url).should.eq '[]'
17
- end
18
-
19
- stub_request(:head , url).to_return(:headers => {'A' => 'B'})
20
- c. head(url).should.eq('A' => 'B')
21
-
22
- stub_request(:options, url).to_return(:headers => {'A' => 'B'})
23
- c.options(url).should.eq('A' => 'B')
24
- end
25
-
26
- would 'call the callback' do
27
- [:get, :post, :delete, :put, :patch].each do |method|
28
- stub_request(method, url).to_return(:body => '123')
29
- (client = RC::Simple.new).send(method, url){ |res|
30
- res.should.eq '123' }.should.eq client
31
- client.wait
32
- end
33
-
34
- stub_request(:head, url).to_return(:headers => {'A' => 'B'})
35
- (client = RC::Simple.new).head(url){ |res|
36
- res.should.eq({'A' => 'B'})
37
- }.should.eq client
38
- client.wait
39
-
40
- stub_request(:options, url).to_return(:headers => {'A' => 'B'})
41
- (client = RC::Simple.new).options(url){ |res|
42
- res.should.eq('A' => 'B')
43
- }.should.eq client
44
- client.wait
45
- end
46
-
47
- would 'wait for all the requests' do
48
- t, i, m = 5, 0, Mutex.new
49
- stub_request(:get, url).to_return do
50
- m.synchronize{ i += 1 }
51
- Thread.pass
52
- {}
53
- end
54
-
55
- client = RC::Builder.client
56
- t.times{ client.new.get(url) }
57
- client.wait
58
- client.promises.should.empty?
59
- i.should.eq t
60
- end
61
-
62
- would 'wait for callback' do
63
- rd, wr = IO.pipe
64
- called = false
65
- stub_request(:get, url).to_return(:body => 'nnf')
66
- client = RC::Builder.client.new.get(url) do |nnf|
67
- wr.puts
68
- sleep 0.001 # make sure our callback is slow enough,
69
- # so that if `wait` is not waiting for the callback,
70
- # it would leave before the callback is completely done.
71
- # without sleeping, the callback is very likely to be
72
- # done first than `wait` anyway. raising the sleeping time
73
- # would make this test more reliable...
74
- called = true
75
- nil # test against returning nil, so that Promise#response is not set
76
- end
77
- rd.gets
78
- client.wait
79
- called.should.eq true
80
- end
81
-
82
- would 'cleanup promises' do
83
- stub_request(:get, url).to_return(:body => 'nnf')
84
- client = RC::Builder.client
85
- 5.times{ client.new.get(url) }
86
- Thread.pass
87
- GC.start # can only force GC run on MRI, so we mock for jruby and rubinius
88
- stub(any_instance_of(WeakRef)).weakref_alive?{false}
89
- client.new.get(url)
90
- client.promises.size.should.lt 6
91
- client.shutdown
92
- client.promises.should.empty?
93
- end
94
-
95
- would 'have correct to_i' do
96
- stub_request(:get, url).to_return(:body => '123')
97
- RC::Simple.new.get(url).to_i.should.eq 123
98
- end
99
-
100
- would 'use defaults' do
101
- client = RC::Builder.client do
102
- use RC::Timeout, 4
103
- end
104
- c = client.new
105
- c.timeout.should.eq 4 # default goes to middleware
106
- client.extend(Module.new do
107
- def default_timeout
108
- 3
109
- end
110
- end)
111
- c.timeout.should.eq 4 # default is cached, so it stays the same
112
- c.timeout = nil # clear cache
113
- c.timeout.should.eq 3 # now default goes to module default
114
- class << client
115
- def default_timeout # module defaults could be overriden
116
- super - 1
117
- end
118
- end
119
- c.timeout = nil
120
- c.timeout.should.eq 2 # so it goes to class default
121
- c.timeout = 1 # setup instance level value
122
- c.build_env( )['timeout'].should.eq 1 # pick instance var
123
- c.build_env({'timeout' => 0})['timeout'].should.eq 0 # per-request var
124
- c.timeout.should.eq 1 # won't affect underlying instance var
125
- c.timeout = nil
126
- c.timeout.should.eq 2 # goes back to class default
127
- c.timeout = false
128
- c.timeout.should.eq false # false would disable default
129
- end
130
-
131
- would 'work for inheritance' do
132
- stub_request(:get, url).to_return(:body => '123')
133
- Class.new(RC::Simple).new.get(url).should.eq '123'
134
- end
135
-
136
- would 'not deadlock when exception was raised in the callback' do
137
- client = Class.new(RC::Simple).new
138
- stub_request(:get, url).to_return(:body => 'nnf')
139
-
140
- (-1..1).each do |size|
141
- mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
142
- msg.should.include?('nnf')
143
- end
144
- client.class.pool_size = size
145
- client.get(url) do |body|
146
- raise body
147
- end
148
- client.class.shutdown
149
- end
150
- end
151
-
152
- would 'be able to access caller outside the callback' do
153
- client = RC::Simple.new
154
- stub_request(:get, url).to_return(:body => 'nnf')
155
- client.get(url) do
156
- current_file = /^#{__FILE__}/
157
- caller.grep(current_file).should.empty?
158
- RC::Promise.backtrace.grep(current_file).should.not.empty?
159
- client.get(url) do
160
- RC::Promise.backtrace.last.should.not =~ /promise\.rb:\d+:in/
161
- client = nil
162
- end
163
- end
164
- client.wait
165
- client.should.nil? # to make sure the inner most block did run
166
- end
167
-
168
- would 'call error_callback' do
169
- error = nil
170
- error_callback = lambda{ |e| error = e }
171
- should.raise(Errno::ECONNREFUSED) do
172
- RC::Simple.new(:error_callback => error_callback).
173
- get('http://localhost:1').tap{}
174
- end
175
- error.should.kind_of?(Errno::ECONNREFUSED)
176
- end
177
- end