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