david 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +2 -2
- data/Gemfile +8 -2
- data/Gemfile.lock +84 -44
- data/README.md +87 -5
- data/benchmarks/Gemfile +1 -0
- data/benchmarks/Gemfile.lock +3 -1
- data/benchmarks/coapbench.sh +20 -0
- data/benchmarks/quick.sh +2 -0
- data/benchmarks/rackup/Gemfile +10 -0
- data/benchmarks/rackup/Gemfile.lock +159 -0
- data/benchmarks/rackup/grape.ru +18 -0
- data/benchmarks/rackup/rack.ru +7 -0
- data/benchmarks/rackup/rails.ru +14 -0
- data/benchmarks/rps.rb +14 -2
- data/benchmarks/stress.sh +17 -0
- data/david.gemspec +0 -3
- data/experiments/Gemfile +6 -0
- data/experiments/concurrency/Gemfile +3 -0
- data/experiments/concurrency/stub.rb +88 -0
- data/experiments/hash_key.rb +64 -0
- data/experiments/string_concat.rb +21 -0
- data/experiments/symbol_to_proc.rb +15 -0
- data/experiments/thread_safe.rb +40 -0
- data/lib/david.rb +8 -4
- data/lib/david/actor.rb +1 -11
- data/lib/david/app_config.rb +112 -0
- data/lib/david/exchange.rb +124 -0
- data/lib/david/fake_logger.rb +11 -0
- data/lib/david/garbage_collector.rb +9 -17
- data/lib/david/guerilla/rack/utils.rb +18 -0
- data/lib/david/interop.rb +4 -0
- data/lib/david/interop/mandatory_etsi.rb +4 -0
- data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
- data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
- data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
- data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
- data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
- data/lib/david/observe.rb +24 -29
- data/lib/david/rails/action_controller/base.rb +9 -7
- data/lib/david/railties/config.rb +4 -4
- data/lib/david/registry.rb +22 -0
- data/lib/david/server.rb +56 -56
- data/lib/david/server/constants.rb +1 -0
- data/lib/david/server/mapping.rb +43 -11
- data/lib/david/server/mid_cache.rb +29 -0
- data/lib/david/server/multicast.rb +7 -5
- data/lib/david/server/respond.rb +53 -43
- data/lib/david/server/utility.rb +2 -1
- data/lib/david/show_exceptions.rb +12 -8
- data/lib/david/transmitter.rb +44 -0
- data/lib/david/trap.rb +10 -0
- data/lib/david/version.rb +1 -1
- data/lib/rack/handler/david.rb +6 -10
- data/lib/rack/hello_world.rb +25 -0
- data/spec/app_config_spec.rb +56 -0
- data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
- data/spec/dummy/app/controllers/tests_controller.rb +9 -0
- data/spec/dummy/config/application.rb +4 -6
- data/spec/dummy/config/environments/development.rb +2 -2
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/config/routes.rb +13 -53
- data/spec/interop/mandatory_spec.rb +100 -0
- data/spec/observe_spec.rb +8 -7
- data/spec/resource_discovery_spec.rb +3 -3
- data/spec/server_spec.rb +60 -15
- data/spec/spec_helper.rb +21 -2
- metadata +40 -33
- data/lib/david/request.rb +0 -80
- data/lib/david/server/deduplication.rb +0 -21
- data/lib/david/server/options.rb +0 -79
@@ -0,0 +1,124 @@
|
|
1
|
+
module David
|
2
|
+
class Exchange < Struct.new(:host, :port, :message, :ancillary, :options)
|
3
|
+
include Registry
|
4
|
+
|
5
|
+
def ==(other)
|
6
|
+
mid == other.mid && token == other.token
|
7
|
+
end
|
8
|
+
|
9
|
+
def accept
|
10
|
+
message.options[:accept]
|
11
|
+
end
|
12
|
+
|
13
|
+
def ack?
|
14
|
+
message.tt == :ack
|
15
|
+
end
|
16
|
+
|
17
|
+
def block
|
18
|
+
@block ||= if message.options[:block2].nil?
|
19
|
+
CoAP::Block.new(0, false, 1024)
|
20
|
+
else
|
21
|
+
CoAP::Block.new(message.options[:block2]).decode
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def cbor?
|
26
|
+
message.options[:content_format] == 60
|
27
|
+
end
|
28
|
+
|
29
|
+
def con?
|
30
|
+
message.tt == :con
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete?
|
34
|
+
message.mcode == :delete
|
35
|
+
end
|
36
|
+
|
37
|
+
def etag
|
38
|
+
message.options[:etag]
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_etag?
|
42
|
+
message.options[:etag].nil? && get?
|
43
|
+
end
|
44
|
+
|
45
|
+
def get?
|
46
|
+
message.mcode == :get
|
47
|
+
end
|
48
|
+
|
49
|
+
def idempotent?
|
50
|
+
get? || put? || delete?
|
51
|
+
end
|
52
|
+
|
53
|
+
def key
|
54
|
+
[host, mid]
|
55
|
+
end
|
56
|
+
|
57
|
+
def mid
|
58
|
+
message.mid
|
59
|
+
end
|
60
|
+
|
61
|
+
def multicast?
|
62
|
+
a = ancillary
|
63
|
+
return false if a.nil?
|
64
|
+
|
65
|
+
return @multicast unless @multicast.nil?
|
66
|
+
|
67
|
+
@multicast =
|
68
|
+
a.cmsg_is?(:IP, :PKTINFO) && a.ip_pktinfo[0].ipv4_multicast? ||
|
69
|
+
a.cmsg_is?(:IPV6, :PKTINFO) && a.ipv6_pktinfo[0].ipv6_multicast?
|
70
|
+
end
|
71
|
+
|
72
|
+
def non?
|
73
|
+
message.tt == :non
|
74
|
+
end
|
75
|
+
|
76
|
+
def observe?
|
77
|
+
!message.options[:observe].nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def post?
|
81
|
+
message.mcode == :post
|
82
|
+
end
|
83
|
+
|
84
|
+
def proxy?
|
85
|
+
!(message.options[:proxy_uri].nil? && message.options[:proxy_scheme].nil?)
|
86
|
+
end
|
87
|
+
|
88
|
+
def put?
|
89
|
+
message.mcode == :put
|
90
|
+
end
|
91
|
+
|
92
|
+
def reliable?
|
93
|
+
con? || ack?
|
94
|
+
end
|
95
|
+
|
96
|
+
def request?
|
97
|
+
con? || non?
|
98
|
+
end
|
99
|
+
|
100
|
+
def response?
|
101
|
+
ack? || rst?
|
102
|
+
end
|
103
|
+
|
104
|
+
def rst?
|
105
|
+
message.tt == :rst
|
106
|
+
end
|
107
|
+
|
108
|
+
def separate?
|
109
|
+
ack? && message.payload.empty? && message.mcode == [0, 0]
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s
|
113
|
+
"[#{host}]:#{port}: #{message} (block #{block.num})"
|
114
|
+
end
|
115
|
+
|
116
|
+
def token
|
117
|
+
message.options[:token]
|
118
|
+
end
|
119
|
+
|
120
|
+
def valid_method?
|
121
|
+
CoAP::METHODS.include?(message.mcode)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -2,33 +2,25 @@ module David
|
|
2
2
|
class GarbageCollector
|
3
3
|
include Actor
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@tick_interval = tick_interval
|
7
|
-
@
|
5
|
+
def initialize(options = {})
|
6
|
+
@tick_interval = options[:tick_interval] || 10
|
7
|
+
@timeout = options[:timeout] || 5
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
log.debug 'GarbageCollector initialized'
|
12
|
-
end
|
9
|
+
log.debug('GarbageCollector initialized')
|
13
10
|
|
14
|
-
|
15
|
-
server.dedup_cache.delete_if do |k, v|
|
16
|
-
now - v[1] >= @dedup_timeout
|
17
|
-
end
|
11
|
+
async.run
|
18
12
|
end
|
19
13
|
|
20
14
|
private
|
21
15
|
|
22
16
|
def run
|
23
|
-
|
17
|
+
every(@tick_interval) { tick }
|
24
18
|
end
|
25
19
|
|
26
20
|
def tick
|
27
|
-
unless server.
|
28
|
-
log.debug
|
29
|
-
|
30
|
-
clean_dedup_cache
|
31
|
-
log.debug server.dedup_cache
|
21
|
+
unless server.cache.empty?
|
22
|
+
log.debug('GarbageCollector tick')
|
23
|
+
server.cache_clean!(@timeout)
|
32
24
|
end
|
33
25
|
end
|
34
26
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Monkey-patch Rack to accept Float status codes.
|
2
|
+
# https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L647
|
3
|
+
module Rack
|
4
|
+
module Utils
|
5
|
+
def status_code(status)
|
6
|
+
case status
|
7
|
+
when Symbol
|
8
|
+
SYMBOL_TO_STATUS_CODE[status] || 500
|
9
|
+
when Float
|
10
|
+
status
|
11
|
+
else
|
12
|
+
status.to_i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module_function :status_code
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module David::Interop::MandatoryETSI
|
2
|
+
class Grape < ::Grape::API
|
3
|
+
content_type :txt, 'text/plain'
|
4
|
+
default_format :txt
|
5
|
+
|
6
|
+
get :test do
|
7
|
+
# Grape calls #to_i on status and resets headers on 205.
|
8
|
+
status 200
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
post :test do
|
13
|
+
status 201
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
put :test do
|
18
|
+
status 204
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
delete :test do
|
23
|
+
status 202
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
get 'seg1/seg2/seg3' do
|
28
|
+
status 200
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
get 'query' do
|
33
|
+
status 200
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module David::Interop::MandatoryETSI
|
2
|
+
class Hobbit < ::Hobbit::Base
|
3
|
+
get '/test' do
|
4
|
+
response.status = 2.05
|
5
|
+
response['Content-Type'] = 'text/plain'
|
6
|
+
end
|
7
|
+
|
8
|
+
post '/test' do
|
9
|
+
response.status = 2.01
|
10
|
+
end
|
11
|
+
|
12
|
+
put '/test' do
|
13
|
+
response.status = 2.04
|
14
|
+
end
|
15
|
+
|
16
|
+
delete '/test' do
|
17
|
+
response.status = 2.02
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/seg1/seg2/seg3' do
|
21
|
+
response.status = 2.05
|
22
|
+
response['Content-Type'] = 'text/plain'
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/query' do
|
26
|
+
response.status = 2.05
|
27
|
+
response['Content-Type'] = 'text/plain'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module David::Interop::MandatoryETSI
|
2
|
+
class NYNY < ::NYNY::App
|
3
|
+
before { headers['Content-Type'] = 'text/plain' }
|
4
|
+
|
5
|
+
get '/test' do
|
6
|
+
# NYNY calls #to_i on status and resets headers on 205.
|
7
|
+
status 200
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
post '/test' do
|
12
|
+
status 201
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
put '/test' do
|
17
|
+
status 204
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
delete '/test' do
|
22
|
+
status 202
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/seg1/seg2/seg3' do
|
27
|
+
status 200
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/query' do
|
32
|
+
status 200
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module David::Interop::MandatoryETSI
|
2
|
+
class Rack
|
3
|
+
EMPTY_CONTENT = [2.05, {'Content-Type' => 'text/plain'}, []]
|
4
|
+
|
5
|
+
def call(env)
|
6
|
+
return case request(env)
|
7
|
+
when 'GET /test', 'GET /seg1/seg2/seg3', 'GET /query'
|
8
|
+
EMPTY_CONTENT
|
9
|
+
when 'POST /test'
|
10
|
+
[2.01, {}, []]
|
11
|
+
when 'PUT /test'
|
12
|
+
[2.04, {}, []]
|
13
|
+
when 'DELETE /test'
|
14
|
+
[2.02, {}, []]
|
15
|
+
else
|
16
|
+
[4.04, {}, []]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def request(env)
|
23
|
+
env['REQUEST_METHOD'] + ' ' + env['PATH_INFO']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module David::Interop::MandatoryETSI
|
2
|
+
class Sinatra < ::Sinatra::Base
|
3
|
+
before { content_type 'text/plain' }
|
4
|
+
|
5
|
+
get '/test' do
|
6
|
+
# Sinatra calls #to_i on status and resets headers on 205.
|
7
|
+
status 200
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
post '/test' do
|
12
|
+
status 201
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
put '/test' do
|
17
|
+
status 204
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
delete '/test' do
|
22
|
+
status 202
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/seg1/seg2/seg3' do
|
27
|
+
status 200
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/query' do
|
32
|
+
status 200
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/david/observe.rb
CHANGED
@@ -12,21 +12,21 @@ module David
|
|
12
12
|
log.debug 'Observe initialized'
|
13
13
|
end
|
14
14
|
|
15
|
-
def add(
|
16
|
-
|
17
|
-
|
15
|
+
def add(exchange, env, etag)
|
16
|
+
exchange.message.tt = :non
|
17
|
+
exchange.message.mid = nil
|
18
|
+
exchange.message.options.delete(:observe)
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
[0, request, env, etag, Time.now.to_i]
|
20
|
+
self[[exchange.host, exchange.token]] ||=
|
21
|
+
[0, exchange, env, etag, Time.now.to_i]
|
22
22
|
end
|
23
23
|
|
24
|
-
def delete(
|
25
|
-
_delete([
|
24
|
+
def delete(exchange)
|
25
|
+
_delete([exchange.host, exchange.token])
|
26
26
|
end
|
27
27
|
|
28
|
-
def include?(
|
29
|
-
_include?([
|
28
|
+
def include?(exchange)
|
29
|
+
_include?([exchange.host, exchange.token])
|
30
30
|
end
|
31
31
|
|
32
32
|
def to_s
|
@@ -44,50 +44,45 @@ module David
|
|
44
44
|
# TODO If ETag did not change but max-age of last notification is expired,
|
45
45
|
# return empty 2.03.
|
46
46
|
def handle_update(key)
|
47
|
-
n,
|
47
|
+
n, exchange, env, etag = self[key]
|
48
48
|
n += 1
|
49
49
|
|
50
|
-
response, options = server.respond(
|
50
|
+
response, options = server.respond(exchange, env)
|
51
51
|
|
52
52
|
return if response.nil?
|
53
53
|
|
54
54
|
if response.mcode[0] != 2
|
55
|
-
self.delete(
|
56
|
-
|
55
|
+
self.delete(exchange)
|
56
|
+
transmit(exchange, response, options)
|
57
57
|
return
|
58
58
|
end
|
59
59
|
|
60
60
|
if etag != response.options[:etag]
|
61
61
|
response.options[:observe] = n
|
62
|
+
transmit(exchange, response, options)
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
if !answer.nil? && answer.tt == :rst
|
66
|
-
|
67
|
-
|
68
|
-
end
|
64
|
+
# TODO Implement removing of observe relationship on RST answer to
|
65
|
+
# notification in main dispatcher
|
66
|
+
# if !answer.nil? && answer.tt == :rst
|
67
|
+
# self.delete(exchange)
|
68
|
+
# return
|
69
|
+
# end
|
69
70
|
|
70
71
|
bump(key, n, response)
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
74
|
-
def
|
75
|
-
answer = nil
|
76
|
-
|
75
|
+
def transmit(exchange, message, options)
|
77
76
|
log.debug message.inspect
|
78
77
|
|
79
78
|
begin
|
80
|
-
|
81
|
-
answer = CoAP::Transmission.request(message, host, port, options).last
|
82
|
-
log.debug answer.inspect
|
79
|
+
server.socket.send(message.to_wire, 0, exchange.host, exchange.port)
|
83
80
|
rescue Timeout::Error, RuntimeError
|
84
81
|
end
|
85
|
-
|
86
|
-
answer
|
87
82
|
end
|
88
83
|
|
89
84
|
def run
|
90
|
-
|
85
|
+
every(@tick_interval) { tick }
|
91
86
|
end
|
92
87
|
|
93
88
|
def tick
|