david 0.3.0 → 0.4.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/.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
|