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,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