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.
- checksums.yaml +4 -4
- data/.gitmodules +3 -0
- data/.travis.yml +0 -1
- data/CHANGES.md +28 -0
- data/Gemfile +0 -2
- data/README.md +14 -95
- data/Rakefile +16 -3
- data/example/simple.rb +1 -0
- data/example/use-cases.rb +15 -3
- data/lib/rest-core.rb +38 -75
- data/lib/rest-core/client/universal.rb +3 -1
- data/lib/rest-core/client_oauth1.rb +64 -59
- data/lib/rest-core/event.rb +9 -11
- data/lib/rest-core/middleware/auth_basic.rb +21 -21
- data/lib/rest-core/middleware/bypass.rb +8 -8
- data/lib/rest-core/middleware/cache.rb +94 -90
- data/lib/rest-core/middleware/clash_response.rb +15 -14
- data/lib/rest-core/middleware/common_logger.rb +27 -26
- data/lib/rest-core/middleware/default_headers.rb +8 -8
- data/lib/rest-core/middleware/default_payload.rb +8 -8
- data/lib/rest-core/middleware/default_query.rb +8 -8
- data/lib/rest-core/middleware/default_site.rb +12 -12
- data/lib/rest-core/middleware/defaults.rb +38 -38
- data/lib/rest-core/middleware/error_detector.rb +10 -10
- data/lib/rest-core/middleware/error_detector_http.rb +6 -4
- data/lib/rest-core/middleware/error_handler.rb +14 -14
- data/lib/rest-core/middleware/follow_redirect.rb +28 -27
- data/lib/rest-core/middleware/json_request.rb +13 -11
- data/lib/rest-core/middleware/json_response.rb +29 -28
- data/lib/rest-core/middleware/oauth1_header.rb +84 -83
- data/lib/rest-core/middleware/oauth2_header.rb +27 -25
- data/lib/rest-core/middleware/oauth2_query.rb +15 -15
- data/lib/rest-core/middleware/query_response.rb +14 -14
- data/lib/rest-core/middleware/retry.rb +25 -23
- data/lib/rest-core/middleware/smash_response.rb +15 -14
- data/lib/rest-core/middleware/timeout.rb +18 -19
- data/lib/rest-core/test.rb +1 -18
- data/lib/rest-core/util/clash.rb +38 -37
- data/lib/rest-core/util/config.rb +40 -39
- data/lib/rest-core/util/dalli_extension.rb +11 -10
- data/lib/rest-core/util/hmac.rb +9 -8
- data/lib/rest-core/util/json.rb +55 -54
- data/lib/rest-core/util/parse_link.rb +13 -12
- data/lib/rest-core/util/parse_query.rb +24 -22
- data/lib/rest-core/version.rb +1 -1
- data/rest-core.gemspec +121 -158
- data/test/test_cache.rb +2 -0
- data/test/test_default_payload.rb +1 -1
- data/test/test_error_handler.rb +5 -4
- data/test/test_timeout.rb +9 -8
- data/test/test_universal.rb +8 -0
- metadata +9 -73
- data/lib/rest-core/builder.rb +0 -162
- data/lib/rest-core/client.rb +0 -277
- data/lib/rest-core/client/simple.rb +0 -2
- data/lib/rest-core/engine.rb +0 -36
- data/lib/rest-core/engine/dry.rb +0 -9
- data/lib/rest-core/engine/http-client.rb +0 -41
- data/lib/rest-core/error.rb +0 -5
- data/lib/rest-core/event_source.rb +0 -137
- data/lib/rest-core/middleware.rb +0 -151
- data/lib/rest-core/promise.rb +0 -249
- data/lib/rest-core/thread_pool.rb +0 -131
- data/lib/rest-core/timer.rb +0 -58
- data/lib/rest-core/util/payload.rb +0 -173
- data/test/test_builder.rb +0 -40
- data/test/test_client.rb +0 -177
- data/test/test_event_source.rb +0 -159
- data/test/test_future.rb +0 -16
- data/test/test_httpclient.rb +0 -118
- data/test/test_payload.rb +0 -204
- data/test/test_promise.rb +0 -146
- data/test/test_simple.rb +0 -38
- data/test/test_thread_pool.rb +0 -10
data/lib/rest-core/engine.rb
DELETED
@@ -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
|
data/lib/rest-core/engine/dry.rb
DELETED
@@ -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
|
data/lib/rest-core/error.rb
DELETED
@@ -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
|
data/lib/rest-core/middleware.rb
DELETED
@@ -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
|
data/lib/rest-core/promise.rb
DELETED
@@ -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
|