david 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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