rest-core 1.0.3 → 2.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.
- data/.travis.yml +6 -7
- data/CHANGES.md +137 -0
- data/Gemfile +1 -1
- data/README.md +183 -191
- data/TODO.md +5 -8
- data/example/multi.rb +31 -24
- data/example/simple.rb +28 -0
- data/example/use-cases.rb +194 -0
- data/lib/rest-core.rb +26 -19
- data/lib/rest-core/builder.rb +2 -2
- data/lib/rest-core/client.rb +40 -27
- data/lib/rest-core/client/universal.rb +16 -13
- data/lib/rest-core/client_oauth1.rb +5 -5
- data/lib/rest-core/engine/auto.rb +25 -0
- data/lib/rest-core/{app → engine}/dry.rb +1 -2
- data/lib/rest-core/engine/em-http-request.rb +39 -0
- data/lib/rest-core/engine/future/future.rb +106 -0
- data/lib/rest-core/engine/future/future_fiber.rb +39 -0
- data/lib/rest-core/engine/future/future_thread.rb +29 -0
- data/lib/rest-core/engine/rest-client.rb +56 -0
- data/lib/rest-core/middleware.rb +27 -5
- data/lib/rest-core/middleware/auth_basic.rb +5 -5
- data/lib/rest-core/middleware/bypass.rb +2 -2
- data/lib/rest-core/middleware/cache.rb +67 -54
- data/lib/rest-core/middleware/common_logger.rb +5 -8
- data/lib/rest-core/middleware/default_headers.rb +2 -2
- data/lib/rest-core/middleware/default_payload.rb +26 -2
- data/lib/rest-core/middleware/default_query.rb +4 -2
- data/lib/rest-core/middleware/default_site.rb +8 -6
- data/lib/rest-core/middleware/error_detector.rb +9 -16
- data/lib/rest-core/middleware/error_handler.rb +25 -11
- data/lib/rest-core/middleware/follow_redirect.rb +11 -14
- data/lib/rest-core/middleware/json_request.rb +19 -0
- data/lib/rest-core/middleware/json_response.rb +28 -0
- data/lib/rest-core/middleware/oauth1_header.rb +2 -7
- data/lib/rest-core/middleware/oauth2_header.rb +4 -7
- data/lib/rest-core/middleware/oauth2_query.rb +2 -2
- data/lib/rest-core/middleware/timeout.rb +21 -65
- data/lib/rest-core/middleware/timeout/{eventmachine_timer.rb → timer_em.rb} +3 -1
- data/lib/rest-core/middleware/timeout/timer_thread.rb +36 -0
- data/lib/rest-core/patch/multi_json.rb +8 -0
- data/lib/rest-core/test.rb +3 -12
- data/lib/rest-core/util/json.rb +65 -0
- data/lib/rest-core/util/parse_query.rb +2 -2
- data/lib/rest-core/version.rb +1 -1
- data/lib/rest-core/wrapper.rb +16 -16
- data/rest-core.gemspec +28 -27
- data/test/test_auth_basic.rb +14 -10
- data/test/test_builder.rb +7 -7
- data/test/test_cache.rb +126 -37
- data/test/test_client.rb +3 -1
- data/test/test_client_oauth1.rb +2 -3
- data/test/test_default_query.rb +17 -23
- data/test/test_em_http_request.rb +146 -0
- data/test/test_error_detector.rb +0 -1
- data/test/test_error_handler.rb +44 -0
- data/test/test_follow_redirect.rb +17 -19
- data/test/test_json_request.rb +28 -0
- data/test/test_json_response.rb +51 -0
- data/test/test_oauth1_header.rb +4 -4
- data/test/test_payload.rb +20 -12
- data/test/test_simple.rb +14 -0
- data/test/test_timeout.rb +11 -19
- data/test/test_universal.rb +5 -5
- data/test/test_wrapper.rb +19 -13
- metadata +28 -29
- data/doc/ToC.md +0 -7
- data/doc/dependency.md +0 -4
- data/doc/design.md +0 -4
- data/example/auto.rb +0 -51
- data/example/coolio.rb +0 -21
- data/example/eventmachine.rb +0 -30
- data/example/rest-client.rb +0 -16
- data/lib/rest-core/app/abstract/async_fiber.rb +0 -13
- data/lib/rest-core/app/auto.rb +0 -23
- data/lib/rest-core/app/coolio-async.rb +0 -32
- data/lib/rest-core/app/coolio-fiber.rb +0 -30
- data/lib/rest-core/app/coolio.rb +0 -9
- data/lib/rest-core/app/em-http-request-async.rb +0 -37
- data/lib/rest-core/app/em-http-request-fiber.rb +0 -45
- data/lib/rest-core/app/em-http-request.rb +0 -9
- data/lib/rest-core/app/rest-client.rb +0 -41
- data/lib/rest-core/middleware/json_decode.rb +0 -93
- data/lib/rest-core/middleware/timeout/coolio_timer.rb +0 -10
- data/pending/test_multi.rb +0 -123
- data/pending/test_test_util.rb +0 -86
- data/test/test_json_decode.rb +0 -24
@@ -1,18 +1,21 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
module RestCore
|
3
|
+
Universal = Builder.client do
|
4
|
+
use Timeout , 0
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
use DefaultSite , nil
|
7
|
+
use DefaultHeaders, {}
|
8
|
+
use DefaultQuery , {}
|
9
|
+
use DefaultPayload, {}
|
10
|
+
use JsonRequest , false
|
11
|
+
use AuthBasic , nil, nil
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
use FollowRedirect, 10
|
14
|
+
use CommonLogger , method(:puts)
|
15
|
+
use Cache , {}, 600 do
|
16
|
+
use ErrorHandler, nil
|
17
|
+
use ErrorDetectorHttp
|
18
|
+
use JsonResponse, false
|
19
|
+
end
|
17
20
|
end
|
18
21
|
end
|
@@ -6,7 +6,7 @@ module RestCore::ClientOauth1
|
|
6
6
|
|
7
7
|
def authorize_url! opts={}
|
8
8
|
self.data = ParseQuery.parse_query(
|
9
|
-
post(request_token_path, {}, {}, {:
|
9
|
+
post(request_token_path, {}, {}, {:json_response => false}.merge(opts)))
|
10
10
|
|
11
11
|
authorize_url
|
12
12
|
end
|
@@ -17,7 +17,7 @@ module RestCore::ClientOauth1
|
|
17
17
|
|
18
18
|
def authorize! opts={}
|
19
19
|
self.data = ParseQuery.parse_query(
|
20
|
-
post(access_token_path, {}, {}, {:
|
20
|
+
post(access_token_path, {}, {}, {:json_response => false}.merge(opts)))
|
21
21
|
|
22
22
|
data['authorized'] = 'true'
|
23
23
|
data
|
@@ -28,12 +28,12 @@ module RestCore::ClientOauth1
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def data_json
|
31
|
-
|
31
|
+
Json.encode(data.merge('sig' => calculate_sig))
|
32
32
|
end
|
33
33
|
|
34
34
|
def data_json= json
|
35
|
-
self.data = check_sig_and_return_data(
|
36
|
-
rescue
|
35
|
+
self.data = check_sig_and_return_data(Json.decode(json))
|
36
|
+
rescue Json.const_get(:ParseError)
|
37
37
|
self.data = nil
|
38
38
|
end
|
39
39
|
|
@@ -0,0 +1,25 @@
|
|
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
|
@@ -0,0 +1,39 @@
|
|
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
|
+
payload = ::RestClient::Payload.generate(env[REQUEST_PAYLOAD])
|
13
|
+
client = ::EventMachine::HttpRequest.new(request_uri(env)).send(
|
14
|
+
env[REQUEST_METHOD],
|
15
|
+
:body => payload && payload.read,
|
16
|
+
:head => payload && payload.headers.
|
17
|
+
merge(env[REQUEST_HEADERS]))
|
18
|
+
|
19
|
+
client.callback{
|
20
|
+
future.wrap{ # callbacks are run in main thread, so we need to wrap it
|
21
|
+
future.on_load(client.response,
|
22
|
+
client.response_header.status,
|
23
|
+
client.response_header)}}
|
24
|
+
|
25
|
+
client.errback{future.wrap{ future.on_error(client.error) }}
|
26
|
+
|
27
|
+
env[TIMER].on_timeout{
|
28
|
+
(client.instance_variable_get(:@callbacks)||[]).clear
|
29
|
+
(client.instance_variable_get(:@errbacks )||[]).clear
|
30
|
+
client.close
|
31
|
+
future.wrap{ future.on_error(env[TIMER].error) }
|
32
|
+
} if env[TIMER]
|
33
|
+
|
34
|
+
env.merge(RESPONSE_BODY => future.proxy_body,
|
35
|
+
RESPONSE_STATUS => future.proxy_status,
|
36
|
+
RESPONSE_HEADERS => future.proxy_headers,
|
37
|
+
FUTURE => future)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,106 @@
|
|
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 on_load body, status, headers
|
62
|
+
env[TIMER].cancel if env[TIMER]
|
63
|
+
synchronize{
|
64
|
+
self.body, self.status, self.headers = body, status, headers
|
65
|
+
begin
|
66
|
+
# under ASYNC callback, should call immediate
|
67
|
+
next_tick{ callback } if immediate
|
68
|
+
rescue Exception => e
|
69
|
+
# nothing we can do here for an asynchronous exception,
|
70
|
+
# so we just log the error
|
71
|
+
logger = method(:warn) # TODO: add error_log_method
|
72
|
+
logger.call "RestCore: ERROR: #{e}\n" \
|
73
|
+
" from #{e.backtrace.inspect}"
|
74
|
+
end
|
75
|
+
}
|
76
|
+
resume # client or response might be waiting
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_error error
|
80
|
+
self.error = if error.kind_of?(Exception)
|
81
|
+
error
|
82
|
+
else
|
83
|
+
Error.new(error || 'unknown')
|
84
|
+
end
|
85
|
+
on_load('', 0, {})
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
attr_accessor :env, :k, :immediate,
|
90
|
+
:response, :body, :status, :headers, :error
|
91
|
+
|
92
|
+
private
|
93
|
+
def synchronize; yield; end
|
94
|
+
# next_tick is used for telling the reactor that there's something else
|
95
|
+
# should be done, don't sleep and don't stop at the moment
|
96
|
+
def next_tick
|
97
|
+
if Object.const_defined?(:EventMachine) && EventMachine.reactor_running?
|
98
|
+
EventMachine.next_tick{ yield }
|
99
|
+
else
|
100
|
+
yield
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
autoload :FutureFiber , 'rest-core/engine/future/future_fiber'
|
105
|
+
autoload :FutureThread, 'rest-core/engine/future/future_thread'
|
106
|
+
end
|
@@ -0,0 +1,39 @@
|
|
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{
|
26
|
+
begin
|
27
|
+
f.resume
|
28
|
+
rescue FiberError
|
29
|
+
# whenever timeout, it would be already resumed,
|
30
|
+
# and we have no way to tell if it's already resumed or not!
|
31
|
+
end
|
32
|
+
}
|
33
|
+
}
|
34
|
+
resume
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
attr_accessor :fibers
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
require 'restclient'
|
3
|
+
require 'rest-core/patch/rest-client'
|
4
|
+
|
5
|
+
require 'rest-core/engine/future/future'
|
6
|
+
require 'rest-core/middleware'
|
7
|
+
|
8
|
+
class RestCore::RestClient
|
9
|
+
include RestCore::Middleware
|
10
|
+
def call env, &k
|
11
|
+
future = Future::FutureThread.new(env, k, env[ASYNC])
|
12
|
+
|
13
|
+
t = future.wrap{ # we can implement thread pool in the future
|
14
|
+
begin
|
15
|
+
res = ::RestClient::Request.execute(:method => env[REQUEST_METHOD ],
|
16
|
+
:url => request_uri(env) ,
|
17
|
+
:payload => env[REQUEST_PAYLOAD],
|
18
|
+
:headers => env[REQUEST_HEADERS],
|
19
|
+
:max_redirects => 0)
|
20
|
+
future.on_load(res.body, res.code, normalize_headers(res.raw_headers))
|
21
|
+
|
22
|
+
rescue ::RestClient::Exception => e
|
23
|
+
if res = e.response
|
24
|
+
# we don't want to raise an exception for 404 requests
|
25
|
+
future.on_load(res.body, res.code,
|
26
|
+
normalize_headers(res.raw_headers))
|
27
|
+
else
|
28
|
+
future.on_error(e)
|
29
|
+
end
|
30
|
+
rescue Exception => e
|
31
|
+
future.on_error(e)
|
32
|
+
end
|
33
|
+
}
|
34
|
+
|
35
|
+
env[TIMER].on_timeout{
|
36
|
+
t.kill
|
37
|
+
future.on_error(env[TIMER].error)
|
38
|
+
} if env[TIMER]
|
39
|
+
|
40
|
+
env.merge(RESPONSE_BODY => future.proxy_body,
|
41
|
+
RESPONSE_STATUS => future.proxy_status,
|
42
|
+
RESPONSE_HEADERS => future.proxy_headers,
|
43
|
+
FUTURE => future)
|
44
|
+
end
|
45
|
+
|
46
|
+
def normalize_headers raw_headers
|
47
|
+
raw_headers.inject({}){ |r, (k, v)|
|
48
|
+
r[k.to_s.upcase.tr('-', '_')] = if v.kind_of?(Array) && v.size == 1
|
49
|
+
v.first
|
50
|
+
else
|
51
|
+
v
|
52
|
+
end
|
53
|
+
r
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
data/lib/rest-core/middleware.rb
CHANGED
@@ -6,11 +6,16 @@ require 'cgi'
|
|
6
6
|
module RestCore::Middleware
|
7
7
|
include RestCore
|
8
8
|
|
9
|
+
# identity function
|
10
|
+
def self.id
|
11
|
+
@id ||= lambda{ |a| a }
|
12
|
+
end
|
13
|
+
|
9
14
|
def self.included mod
|
10
15
|
mod.send(:include, RestCore)
|
11
16
|
mod.send(:attr_reader, :app)
|
12
|
-
|
13
|
-
src =
|
17
|
+
mem = if mod.respond_to?(:members) then mod.members else [] end
|
18
|
+
src = mem.map{ |member| <<-RUBY }
|
14
19
|
def #{member} env
|
15
20
|
if env.key?('#{member}')
|
16
21
|
env['#{member}']
|
@@ -19,7 +24,7 @@ module RestCore::Middleware
|
|
19
24
|
end
|
20
25
|
end
|
21
26
|
RUBY
|
22
|
-
args = [:app] +
|
27
|
+
args = [:app] + mem
|
23
28
|
para_list = args.map{ |a| "#{a}=nil"}.join(', ')
|
24
29
|
args_list = args .join(', ')
|
25
30
|
ivar_list = args.map{ |a| "@#{a}" }.join(', ')
|
@@ -34,9 +39,10 @@ module RestCore::Middleware
|
|
34
39
|
mod.send(:include, accessor)
|
35
40
|
end
|
36
41
|
|
37
|
-
def call env
|
42
|
+
def call env, &k; app.call(env, &(k || id)) ; end
|
38
43
|
def fail env, obj; env.merge(FAIL => (env[FAIL] || []) + [obj]); end
|
39
44
|
def log env, obj; env.merge(LOG => (env[LOG] || []) + [obj]); end
|
45
|
+
def id ; Middleware.id ; end
|
40
46
|
def run app=app
|
41
47
|
if app.respond_to?(:app) && app.app
|
42
48
|
run(app.app)
|
@@ -53,9 +59,25 @@ module RestCore::Middleware
|
|
53
59
|
else
|
54
60
|
q = if env[REQUEST_PATH] =~ /\?/ then '&' else '?' end
|
55
61
|
"#{env[REQUEST_PATH]}#{q}" \
|
56
|
-
"#{query.map{ |(k, v)|
|
62
|
+
"#{query.sort.map{ |(k, v)|
|
57
63
|
"#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')}"
|
58
64
|
end
|
59
65
|
end
|
60
66
|
public :request_uri
|
67
|
+
|
68
|
+
def string_keys hash
|
69
|
+
hash.inject({}){ |r, (k, v)|
|
70
|
+
if v.kind_of?(Hash)
|
71
|
+
r[k.to_s] = case k.to_s
|
72
|
+
when REQUEST_QUERY, REQUEST_PAYLOAD, REQUEST_HEADERS
|
73
|
+
string_keys(v)
|
74
|
+
else; v
|
75
|
+
end
|
76
|
+
else
|
77
|
+
r[k.to_s] = v
|
78
|
+
end
|
79
|
+
r
|
80
|
+
}
|
81
|
+
end
|
82
|
+
public :string_keys
|
61
83
|
end
|
@@ -5,20 +5,20 @@ class RestCore::AuthBasic
|
|
5
5
|
def self.members; [:username, :password]; end
|
6
6
|
include RestCore::Middleware
|
7
7
|
|
8
|
-
def call env
|
8
|
+
def call env, &k
|
9
9
|
if username(env)
|
10
10
|
if password(env)
|
11
11
|
app.call(env.merge(REQUEST_HEADERS =>
|
12
|
-
auth_basic_header(env).merge(env[REQUEST_HEADERS] || {})))
|
12
|
+
auth_basic_header(env).merge(env[REQUEST_HEADERS] || {})), &k)
|
13
13
|
else
|
14
14
|
app.call(log(env, "AuthBasic: username provided: #{username(env)}," \
|
15
|
-
" but password is missing."))
|
15
|
+
" but password is missing."), &k)
|
16
16
|
end
|
17
17
|
elsif password(env)
|
18
18
|
app.call(log(env, "AuthBasic: password provided: #{password(env)}," \
|
19
|
-
" but username is missing."))
|
19
|
+
" but username is missing."), &k)
|
20
20
|
else
|
21
|
-
app.call(env)
|
21
|
+
app.call(env, &k)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|