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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +2 -2
  4. data/Gemfile +8 -2
  5. data/Gemfile.lock +84 -44
  6. data/README.md +87 -5
  7. data/benchmarks/Gemfile +1 -0
  8. data/benchmarks/Gemfile.lock +3 -1
  9. data/benchmarks/coapbench.sh +20 -0
  10. data/benchmarks/quick.sh +2 -0
  11. data/benchmarks/rackup/Gemfile +10 -0
  12. data/benchmarks/rackup/Gemfile.lock +159 -0
  13. data/benchmarks/rackup/grape.ru +18 -0
  14. data/benchmarks/rackup/rack.ru +7 -0
  15. data/benchmarks/rackup/rails.ru +14 -0
  16. data/benchmarks/rps.rb +14 -2
  17. data/benchmarks/stress.sh +17 -0
  18. data/david.gemspec +0 -3
  19. data/experiments/Gemfile +6 -0
  20. data/experiments/concurrency/Gemfile +3 -0
  21. data/experiments/concurrency/stub.rb +88 -0
  22. data/experiments/hash_key.rb +64 -0
  23. data/experiments/string_concat.rb +21 -0
  24. data/experiments/symbol_to_proc.rb +15 -0
  25. data/experiments/thread_safe.rb +40 -0
  26. data/lib/david.rb +8 -4
  27. data/lib/david/actor.rb +1 -11
  28. data/lib/david/app_config.rb +112 -0
  29. data/lib/david/exchange.rb +124 -0
  30. data/lib/david/fake_logger.rb +11 -0
  31. data/lib/david/garbage_collector.rb +9 -17
  32. data/lib/david/guerilla/rack/utils.rb +18 -0
  33. data/lib/david/interop.rb +4 -0
  34. data/lib/david/interop/mandatory_etsi.rb +4 -0
  35. data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
  36. data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
  37. data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
  38. data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
  39. data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
  40. data/lib/david/observe.rb +24 -29
  41. data/lib/david/rails/action_controller/base.rb +9 -7
  42. data/lib/david/railties/config.rb +4 -4
  43. data/lib/david/registry.rb +22 -0
  44. data/lib/david/server.rb +56 -56
  45. data/lib/david/server/constants.rb +1 -0
  46. data/lib/david/server/mapping.rb +43 -11
  47. data/lib/david/server/mid_cache.rb +29 -0
  48. data/lib/david/server/multicast.rb +7 -5
  49. data/lib/david/server/respond.rb +53 -43
  50. data/lib/david/server/utility.rb +2 -1
  51. data/lib/david/show_exceptions.rb +12 -8
  52. data/lib/david/transmitter.rb +44 -0
  53. data/lib/david/trap.rb +10 -0
  54. data/lib/david/version.rb +1 -1
  55. data/lib/rack/handler/david.rb +6 -10
  56. data/lib/rack/hello_world.rb +25 -0
  57. data/spec/app_config_spec.rb +56 -0
  58. data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
  59. data/spec/dummy/app/controllers/tests_controller.rb +9 -0
  60. data/spec/dummy/config/application.rb +4 -6
  61. data/spec/dummy/config/environments/development.rb +2 -2
  62. data/spec/dummy/config/environments/test.rb +2 -2
  63. data/spec/dummy/config/routes.rb +13 -53
  64. data/spec/interop/mandatory_spec.rb +100 -0
  65. data/spec/observe_spec.rb +8 -7
  66. data/spec/resource_discovery_spec.rb +3 -3
  67. data/spec/server_spec.rb +60 -15
  68. data/spec/spec_helper.rb +21 -2
  69. metadata +40 -33
  70. data/lib/david/request.rb +0 -80
  71. data/lib/david/server/deduplication.rb +0 -21
  72. 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
@@ -0,0 +1,11 @@
1
+ module David
2
+ class FakeLogger
3
+ def initialize
4
+ Celluloid.logger = nil
5
+ end
6
+
7
+ [:info, :debug, :warn, :error, :fatal].each do |method|
8
+ define_method(method) { |*args| }
9
+ end
10
+ end
11
+ end
@@ -2,33 +2,25 @@ module David
2
2
  class GarbageCollector
3
3
  include Actor
4
4
 
5
- def initialize(tick_interval = 10, dedup_timeout = 5)
6
- @tick_interval = tick_interval
7
- @dedup_timeout = dedup_timeout
5
+ def initialize(options = {})
6
+ @tick_interval = options[:tick_interval] || 10
7
+ @timeout = options[:timeout] || 5
8
8
 
9
- async.run
10
-
11
- log.debug 'GarbageCollector initialized'
12
- end
9
+ log.debug('GarbageCollector initialized')
13
10
 
14
- def clean_dedup_cache(now = Time.now.to_i)
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
- loop { sleep @tick_interval; tick }
17
+ every(@tick_interval) { tick }
24
18
  end
25
19
 
26
20
  def tick
27
- unless server.dedup_cache.empty?
28
- log.debug 'GarbageCollector tick'
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,4 @@
1
+ module David::Interop
2
+ end
3
+
4
+ require 'david/interop/mandatory_etsi'
@@ -0,0 +1,4 @@
1
+ module David::Interop::MandatoryETSI
2
+ path = File.expand_path('../mandatory_etsi', __FILE__)
3
+ Dir["#{path}/*.rb"].each { |file| require file }
4
+ 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(request, env, etag)
16
- request.message.mid = nil
17
- request.message.options.delete(:observe)
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
- # TODO Check if Array or Struct is more efficient.
20
- self[[request.host, request.token]] ||=
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(request)
25
- _delete([request.host, request.token])
24
+ def delete(exchange)
25
+ _delete([exchange.host, exchange.token])
26
26
  end
27
27
 
28
- def include?(request)
29
- _include?([request.host, request.token])
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, request, env, etag = self[key]
47
+ n, exchange, env, etag = self[key]
48
48
  n += 1
49
49
 
50
- response, options = server.respond(request, env)
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(request)
56
- request(response, request.host, request.port, options)
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
- answer = request(response, request.host, request.port, options)
64
-
65
- if !answer.nil? && answer.tt == :rst
66
- self.delete(request)
67
- return
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 request(message, host, port, options)
75
- answer = nil
76
-
75
+ def transmit(exchange, message, options)
77
76
  log.debug message.inspect
78
77
 
79
78
  begin
80
- options.merge!(retransmit: false, socket: server.socket)
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
- loop { tick; sleep @tick_interval }
85
+ every(@tick_interval) { tick }
91
86
  end
92
87
 
93
88
  def tick